Dan Anglin
3169d341d2
Change Status.Order to Status.Position, and the ByStatusOrder type to ByStatusPosition to make it clearer that status' are ordered by their position on the Kanban board.
312 lines
6.6 KiB
Go
312 lines
6.6 KiB
Go
package board
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/gob"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"codeflow.dananglin.me.uk/apollo/canal/internal/db"
|
|
bolt "go.etcd.io/bbolt"
|
|
)
|
|
|
|
type Board struct {
|
|
db *bolt.DB
|
|
}
|
|
|
|
// Open reads the board from the db.
|
|
// 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 []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(ByStatusPosition(statuses))
|
|
|
|
return statuses, nil
|
|
}
|
|
|
|
// Status returns a single status from the db.
|
|
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)
|
|
}
|
|
|
|
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
|
|
NewContent string
|
|
}
|
|
|
|
// CreateCard creates a card in the db.
|
|
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 db.
|
|
func (b *Board) Card(id int) (Card, error) {
|
|
data, err := db.Read(b.db, db.CardBucket, id)
|
|
if err != nil {
|
|
return Card{}, fmt.Errorf("unable to read card [%d] from the db. %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 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 db.
|
|
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
|
|
}
|
|
|
|
// DeleteCard deletes a card from the db.
|
|
// TODO: finish implementation.
|
|
//func (b *Board) DeleteCard(id int) error {
|
|
// return nil
|
|
//}
|