pelican/internal/board/board.go

342 lines
7.4 KiB
Go

package board
import (
"bytes"
"encoding/gob"
"fmt"
"sort"
"codeflow.dananglin.me.uk/apollo/pelican/internal/db"
bolt "go.etcd.io/bbolt"
)
type Board struct {
db *bolt.DB
}
// Open reads the board from the database.
// If no board exists then a new one will be created.
func Open(path string) (Board, error) {
database, err := db.OpenDatabase(path)
if err != nil {
return Board{}, fmt.Errorf("unable to open the db. %w", err)
}
board := Board{
db: database,
}
statusList, err := board.StatusList()
if err != nil {
return Board{}, err
}
if len(statusList) == 0 {
newStatusList := defaultStatusList()
boltItems := make([]db.BoltItem, len(newStatusList))
for i := range newStatusList {
boltItems[i] = &newStatusList[i]
}
if _, err := db.WriteMany(database, db.StatusBucket, boltItems); err != nil {
return Board{}, fmt.Errorf("unable to save the default status list to the db. %w", err)
}
}
return board, nil
}
// Close closes the project's Kanban board.
func (b *Board) Close() error {
if b.db == nil {
return nil
}
if err := b.db.Close(); err != nil {
return fmt.Errorf("error closing the db. %w", err)
}
return nil
}
// StatusList returns the ordered list of statuses from the db.
func (b *Board) StatusList() ([]Status, error) {
data, err := db.ReadAll(b.db, db.StatusBucket)
if err != nil {
return nil, fmt.Errorf("unable to read the status list, %w", err)
}
statuses := make([]Status, len(data))
for ind, d := range data {
buf := bytes.NewBuffer(d)
decoder := gob.NewDecoder(buf)
var status Status
if err := decoder.Decode(&status); err != nil {
return nil, fmt.Errorf("unable to decode data, %w", err)
}
statuses[ind] = status
}
sort.Sort(ByStatusPosition(statuses))
return statuses, nil
}
// Status returns a single status from the db.
// TODO: Add a test case that handles when a status does not exist.
// Or use in delete status case.
func (b *Board) Status(id int) (Status, error) {
data, err := db.Read(b.db, db.StatusBucket, id)
if err != nil {
return Status{}, fmt.Errorf("unable to read status [%d] from the db. %w", id, err)
}
if data == nil {
return Status{}, StatusNotExistError{ID: id}
}
var status Status
buf := bytes.NewBuffer(data)
decoder := gob.NewDecoder(buf)
if err := decoder.Decode(&status); err != nil {
return Status{}, fmt.Errorf("unable to decode data into a Status object, %w", err)
}
return status, nil
}
type StatusArgs struct {
Name string
Order int
}
// CreateStatus creates a status in the db.
func (b *Board) CreateStatus(args StatusArgs) error {
status := Status{
ID: -1,
Name: args.Name,
Position: args.Order,
CardIds: nil,
}
if _, err := db.Write(b.db, db.StatusBucket, &status); err != nil {
return fmt.Errorf("unable to write the status to the db. %w", err)
}
return nil
}
type UpdateStatusArgs struct {
StatusID int
StatusArgs
}
// UpdateStatus modifies an existing status in the db.
func (b *Board) UpdateStatus(args UpdateStatusArgs) error {
status, err := b.Status(args.StatusID)
if err != nil {
return fmt.Errorf("unable to retrieve the status from the db. %w", err)
}
if len(args.Name) > 0 {
status.Name = args.Name
}
if args.Order > 0 {
status.Position = args.Order
}
if _, err := db.Write(b.db, db.StatusBucket, &status); err != nil {
return fmt.Errorf("unable to write the status to the db. %w", err)
}
return nil
}
// TODO: Finish implementation.
// func (b *Board) DeleteStatus() error {
// return nil
// }
type MoveToStatusArgs struct {
CardID int
CurrentStatusID int
NextStatusID int
}
// MoveToStatus moves a card between statuses.
func (b *Board) MoveToStatus(args MoveToStatusArgs) error {
currentStatus, err := b.Status(args.CurrentStatusID)
if err != nil {
return fmt.Errorf("unable to get the card's current status [%d], %w", args.CurrentStatusID, err)
}
nextStatus, err := b.Status(args.NextStatusID)
if err != nil {
return fmt.Errorf("unable to get the card's next status [%d], %w", args.NextStatusID, err)
}
nextStatus.AddCardID(args.CardID)
currentStatus.RemoveCardID(args.CardID)
boltItems := []db.BoltItem{&currentStatus, &nextStatus}
if _, err := db.WriteMany(b.db, db.StatusBucket, boltItems); err != nil {
return fmt.Errorf("unable to update the statuses in the db. %w", err)
}
return nil
}
type CardArgs struct {
NewTitle string
NewContent string
}
// CreateCard creates a card in the database.
func (b *Board) CreateCard(args CardArgs) (int, error) {
statusList, err := b.StatusList()
if err != nil {
return 0, fmt.Errorf("unable to read the status list, %w", err)
}
if len(statusList) == 0 {
return 0, StatusListEmptyError{}
}
card := Card{
ID: -1,
Title: args.NewTitle,
Content: args.NewContent,
}
cardID, err := db.Write(b.db, db.CardBucket, &card)
if err != nil {
return 0, fmt.Errorf("unable to write card to the db. %w", err)
}
initialStatus := statusList[0]
initialStatus.AddCardID(cardID)
id, err := db.Write(b.db, db.StatusBucket, &initialStatus)
if err != nil {
return 0, fmt.Errorf("unable to write the %s status to the db. %w", initialStatus.Name, err)
}
return id, nil
}
// Card returns a Card value from the database.
// TODO: Handle edge case where the card does not exist in the db.
func (b *Board) Card(cardID int) (Card, error) {
data, err := db.Read(b.db, db.CardBucket, cardID)
if err != nil {
return Card{}, fmt.Errorf("unable to read card [%d] from the db. %w", cardID, err)
}
if data == nil {
return Card{}, CardNotExistError{ID: cardID}
}
var card Card
buf := bytes.NewBuffer(data)
decoder := gob.NewDecoder(buf)
if err := decoder.Decode(&card); err != nil {
return Card{}, fmt.Errorf("unable to decode data, %w", err)
}
return card, nil
}
// CardList returns a list of Card values from the db.
// TODO: function needs testing.
func (b *Board) CardList(ids []int) ([]Card, error) {
data, err := db.ReadMany(b.db, db.CardBucket, ids)
if err != nil {
return nil, fmt.Errorf("unable to read card list from the db. %w", err)
}
cards := make([]Card, len(data))
for ind, d := range data {
buf := bytes.NewBuffer(d)
decoder := gob.NewDecoder(buf)
var card Card
if err := decoder.Decode(&card); err != nil {
return nil, fmt.Errorf("unable to decode data, %w", err)
}
cards[ind] = card
}
return cards, nil
}
type UpdateCardArgs struct {
CardID int
CardArgs
}
// UpdateCard modifies an existing card in the database.
func (b *Board) UpdateCard(args UpdateCardArgs) error {
card, err := b.Card(args.CardID)
if err != nil {
return err
}
if len(args.NewTitle) > 0 {
card.Title = args.NewTitle
}
if len(args.NewContent) > 0 {
card.Content = args.NewContent
}
if _, err := db.Write(b.db, db.CardBucket, &card); err != nil {
return fmt.Errorf("unable to write card to the db. %w", err)
}
return nil
}
type DeleteCardArgs struct {
CardID int
StatusID int
}
// DeleteCard deletes a card from the database.
func (b *Board) DeleteCard(args DeleteCardArgs) error {
if err := db.Delete(b.db, db.CardBucket, args.CardID); err != nil {
return fmt.Errorf("unable to delete the card from the database, %w", err)
}
status, err := b.Status(args.StatusID)
if err != nil {
return fmt.Errorf("unable to read Status '%d' from the database, %w", args.StatusID, err)
}
status.RemoveCardID(args.CardID)
if _, err := db.Write(b.db, db.StatusBucket, &status); err != nil {
return fmt.Errorf("unable to update the status in the database, %w", err)
}
return nil
}