package board import ( "bytes" "encoding/gob" "fmt" "sort" "codeflow.dananglin.me.uk/apollo/canal/internal/database" 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) { db, err := database.OpenDatabase(path) if err != nil { return Board{}, fmt.Errorf("unable to open the database, %w", err) } board := Board{ db: db, } statusList, err := board.StatusList() if err != nil { return Board{}, err } if len(statusList) == 0 { newStatusList := defaultStatusList() boltItems := make([]database.BoltItem, len(newStatusList)) for i := range newStatusList { boltItems[i] = &newStatusList[i] } if _, err := database.WriteMany(db, database.StatusBucket, boltItems); err != nil { return Board{}, fmt.Errorf("unable to save the default status list to the database, %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 database, %w", err) } return nil } // StatusList returns the ordered list of statuses from the database. func (b *Board) StatusList() ([]Status, error) { data, err := database.ReadAll(b.db, database.StatusBucket) if err != nil { return []Status{}, 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 []Status{}, fmt.Errorf("unable to decode data, %w", err) } statuses[ind] = status } sort.Sort(ByStatusOrder(statuses)) return statuses, nil } // Status returns a single status from the database. func (b *Board) Status(id int) (Status, error) { data, err := database.Read(b.db, database.StatusBucket, id) if err != nil { return Status{}, fmt.Errorf("unable to read status [%d] from the database, %w", id, err) } 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 database. func (b *Board) CreateStatus(args StatusArgs) error { status := Status{ ID: -1, Name: args.Name, Order: args.Order, CardIds: nil, } if _, err := database.Write(b.db, database.StatusBucket, &status); err != nil { return fmt.Errorf("unable to write the status to the database, %w", err) } return nil } type UpdateStatusArgs struct { StatusID int StatusArgs } // UpdateStatus modifies an existing status in the database. 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 database, %w", err) } if len(args.Name) > 0 { status.Name = args.Name } if args.Order > 0 { status.Order = args.Order } if _, err := database.Write(b.db, database.StatusBucket, &status); err != nil { return fmt.Errorf("unable to write the status to the database, %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 := []database.BoltItem{¤tStatus, &nextStatus} if _, err := database.WriteMany(b.db, database.StatusBucket, boltItems); err != nil { return fmt.Errorf("unable to update the statuses in the database, %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 := database.Write(b.db, database.CardBucket, &card) if err != nil { return 0, fmt.Errorf("unable to write card to the database, %w", err) } initialStatus := statusList[0] initialStatus.AddCardID(cardID) id, err := database.Write(b.db, database.StatusBucket, &initialStatus) if err != nil { return 0, fmt.Errorf("unable to write the %s status to the database, %w", initialStatus.Name, err) } return id, nil } // Card returns a Card value from the database. func (b *Board) Card(id int) (Card, error) { data, err := database.Read(b.db, database.CardBucket, id) if err != nil { return Card{}, fmt.Errorf("unable to read card [%d] from the database, %w", id, err) } 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 database. // TODO: function needs testing. func (b *Board) CardList(ids []int) ([]Card, error) { data, err := database.ReadMany(b.db, database.CardBucket, ids) if err != nil { return nil, fmt.Errorf("unable to read card list from the database, %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 := database.Write(b.db, database.CardBucket, &card); err != nil { return fmt.Errorf("unable to write card to the database, %w", err) } return nil } // DeleteCard deletes a card from the database. // TODO: finish implementation. //func (b *Board) DeleteCard(id int) error { // return nil //}