Dan Anglin
f956b7da59
Change the content field to description for the card type in preparation for supporting card notes.
346 lines
7.5 KiB
Go
346 lines
7.5 KiB
Go
package board
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/gob"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"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{¤tStatus, &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
|
|
NewDescription string
|
|
}
|
|
|
|
// CreateCard creates a card in the database.
|
|
func (b *Board) CreateCard(args CardArgs) (int, error) {
|
|
timestamp := time.Now().Format(time.DateTime)
|
|
|
|
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,
|
|
Description: args.NewDescription,
|
|
Created: timestamp,
|
|
}
|
|
|
|
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.NewDescription) > 0 {
|
|
card.Description = args.NewDescription
|
|
}
|
|
|
|
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
|
|
}
|