2021-09-18 01:03:09 +01:00
|
|
|
package board
|
|
|
|
|
|
|
|
import (
|
2021-09-23 21:14:35 +01:00
|
|
|
"bytes"
|
|
|
|
"encoding/gob"
|
2021-09-18 01:03:09 +01:00
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
|
2023-02-14 07:54:10 +00:00
|
|
|
"codeflow.dananglin.me.uk/apollo/canal/internal/database"
|
2021-09-18 01:03:09 +01:00
|
|
|
bolt "go.etcd.io/bbolt"
|
|
|
|
)
|
|
|
|
|
2023-04-22 13:18:13 +01:00
|
|
|
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) {
|
2021-09-18 01:03:09 +01:00
|
|
|
db, err := database.OpenDatabase(path)
|
|
|
|
if err != nil {
|
2023-04-22 13:18:13 +01:00
|
|
|
return Board{}, fmt.Errorf("unable to open the database, %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
board := Board{
|
|
|
|
db: db,
|
2021-09-18 01:03:09 +01:00
|
|
|
}
|
|
|
|
|
2023-04-22 13:18:13 +01:00
|
|
|
statusList, err := board.StatusList()
|
2021-09-18 01:03:09 +01:00
|
|
|
if err != nil {
|
2023-04-22 13:18:13 +01:00
|
|
|
return Board{}, err
|
2021-09-18 01:03:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(statusList) == 0 {
|
2021-09-23 21:14:35 +01:00
|
|
|
newStatusList := defaultStatusList()
|
|
|
|
|
|
|
|
boltItems := make([]database.BoltItem, len(newStatusList))
|
|
|
|
|
|
|
|
for i := range newStatusList {
|
|
|
|
boltItems[i] = &newStatusList[i]
|
|
|
|
}
|
|
|
|
|
2021-10-09 16:22:18 +01:00
|
|
|
if _, err := database.WriteMany(db, database.StatusBucket, boltItems); err != nil {
|
2023-04-22 13:18:13 +01:00
|
|
|
return Board{}, fmt.Errorf("unable to save the default status list to the database, %w", err)
|
2021-09-18 01:03:09 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-22 13:18:13 +01:00
|
|
|
return board, nil
|
|
|
|
}
|
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
// Close closes the project's Kanban board.
|
2023-04-22 13:18:13 +01:00
|
|
|
func (b *Board) Close() error {
|
2023-04-22 19:18:47 +01:00
|
|
|
if b.db == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
if err := b.db.Close(); err != nil {
|
|
|
|
return fmt.Errorf("error closing the database, %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2021-09-18 01:03:09 +01:00
|
|
|
}
|
|
|
|
|
2023-04-22 13:18:13 +01:00
|
|
|
// StatusList returns the ordered list of statuses from the database.
|
|
|
|
func (b *Board) StatusList() ([]Status, error) {
|
|
|
|
data, err := database.ReadAll(b.db, database.StatusBucket)
|
2021-09-18 01:03:09 +01:00
|
|
|
if err != nil {
|
2021-09-23 21:14:35 +01:00
|
|
|
return []Status{}, fmt.Errorf("unable to read the status list, %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
statuses := make([]Status, len(data))
|
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
for ind, d := range data {
|
2021-09-23 21:14:35 +01:00
|
|
|
buf := bytes.NewBuffer(d)
|
|
|
|
|
|
|
|
decoder := gob.NewDecoder(buf)
|
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
var status Status
|
2021-09-23 21:14:35 +01:00
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
if err := decoder.Decode(&status); err != nil {
|
2021-09-23 21:14:35 +01:00
|
|
|
return []Status{}, fmt.Errorf("unable to decode data, %w", err)
|
|
|
|
}
|
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
statuses[ind] = status
|
2021-09-18 01:03:09 +01:00
|
|
|
}
|
|
|
|
|
2021-09-23 21:14:35 +01:00
|
|
|
sort.Sort(ByStatusOrder(statuses))
|
2021-09-18 01:03:09 +01:00
|
|
|
|
|
|
|
return statuses, nil
|
|
|
|
}
|
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
// 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
|
2021-09-18 01:03:09 +01:00
|
|
|
}
|
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2021-09-18 01:03:09 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2021-09-18 01:03:09 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Finish implementation.
|
2023-04-26 08:32:33 +01:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2021-09-18 01:03:09 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-22 06:19:50 +01:00
|
|
|
type CardArgs struct {
|
2023-04-23 04:27:16 +01:00
|
|
|
NewTitle string
|
2023-04-22 06:19:50 +01:00
|
|
|
NewContent string
|
|
|
|
}
|
|
|
|
|
2021-09-18 01:03:09 +01:00
|
|
|
// CreateCard creates a card in the database.
|
2023-04-26 08:32:33 +01:00
|
|
|
func (b *Board) CreateCard(args CardArgs) (int, error) {
|
2023-04-22 13:18:13 +01:00
|
|
|
statusList, err := b.StatusList()
|
2021-09-18 01:03:09 +01:00
|
|
|
if err != nil {
|
2023-04-26 08:32:33 +01:00
|
|
|
return 0, fmt.Errorf("unable to read the status list, %w", err)
|
2021-09-18 01:03:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(statusList) == 0 {
|
2023-04-26 08:32:33 +01:00
|
|
|
return 0, statusListEmptyError{}
|
2021-09-18 01:03:09 +01:00
|
|
|
}
|
|
|
|
|
2021-09-23 21:14:35 +01:00
|
|
|
card := Card{
|
2021-09-18 01:03:09 +01:00
|
|
|
ID: -1,
|
2021-10-09 16:22:18 +01:00
|
|
|
Title: args.NewTitle,
|
|
|
|
Content: args.NewContent,
|
2021-09-18 01:03:09 +01:00
|
|
|
}
|
|
|
|
|
2023-04-22 13:18:13 +01:00
|
|
|
cardID, err := database.Write(b.db, database.CardBucket, &card)
|
2021-09-18 01:03:09 +01:00
|
|
|
if err != nil {
|
2023-04-26 08:32:33 +01:00
|
|
|
return 0, fmt.Errorf("unable to write card to the database, %w", err)
|
2021-09-18 01:03:09 +01:00
|
|
|
}
|
|
|
|
|
2021-09-23 21:14:35 +01:00
|
|
|
initialStatus := statusList[0]
|
2021-09-18 01:03:09 +01:00
|
|
|
|
2021-09-23 21:14:35 +01:00
|
|
|
initialStatus.AddCardID(cardID)
|
2021-09-18 01:03:09 +01:00
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
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)
|
2021-09-18 01:03:09 +01:00
|
|
|
}
|
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
return id, nil
|
2021-09-18 01:03:09 +01:00
|
|
|
}
|
|
|
|
|
2023-04-22 13:18:13 +01:00
|
|
|
// 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)
|
2021-09-18 01:03:09 +01:00
|
|
|
if err != nil {
|
2021-09-23 21:14:35 +01:00
|
|
|
return Card{}, fmt.Errorf("unable to read card [%d] from the database, %w", id, err)
|
2021-09-18 01:03:09 +01:00
|
|
|
}
|
|
|
|
|
2021-09-23 21:14:35 +01:00
|
|
|
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
|
2021-09-18 01:03:09 +01:00
|
|
|
}
|
|
|
|
|
2023-04-22 13:18:13 +01:00
|
|
|
// CardList returns a list of Card values from the database.
|
2021-10-09 16:22:18 +01:00
|
|
|
// TODO: function needs testing.
|
2023-04-22 13:18:13 +01:00
|
|
|
func (b *Board) CardList(ids []int) ([]Card, error) {
|
|
|
|
data, err := database.ReadMany(b.db, database.CardBucket, ids)
|
2021-10-09 16:22:18 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to read card list from the database, %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
cards := make([]Card, len(data))
|
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
for ind, d := range data {
|
2021-10-09 16:22:18 +01:00
|
|
|
buf := bytes.NewBuffer(d)
|
|
|
|
|
|
|
|
decoder := gob.NewDecoder(buf)
|
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
var card Card
|
2021-10-09 16:22:18 +01:00
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
if err := decoder.Decode(&card); err != nil {
|
2021-10-09 16:22:18 +01:00
|
|
|
return nil, fmt.Errorf("unable to decode data, %w", err)
|
|
|
|
}
|
|
|
|
|
2023-04-26 08:32:33 +01:00
|
|
|
cards[ind] = card
|
2021-10-09 16:22:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return cards, nil
|
|
|
|
}
|
|
|
|
|
2023-04-22 06:19:50 +01:00
|
|
|
type UpdateCardArgs struct {
|
|
|
|
CardID int
|
|
|
|
CardArgs
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateCard modifies an existing card in the database.
|
2023-04-22 13:18:13 +01:00
|
|
|
func (b *Board) UpdateCard(args UpdateCardArgs) error {
|
|
|
|
card, err := b.Card(args.CardID)
|
2021-09-18 01:03:09 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-10-09 16:22:18 +01:00
|
|
|
if len(args.NewTitle) > 0 {
|
|
|
|
card.Title = args.NewTitle
|
2021-09-23 21:14:35 +01:00
|
|
|
}
|
|
|
|
|
2021-10-09 16:22:18 +01:00
|
|
|
if len(args.NewContent) > 0 {
|
|
|
|
card.Content = args.NewContent
|
2021-09-23 21:14:35 +01:00
|
|
|
}
|
2021-09-18 01:03:09 +01:00
|
|
|
|
2023-04-22 13:18:13 +01:00
|
|
|
if _, err := database.Write(b.db, database.CardBucket, &card); err != nil {
|
2021-09-18 01:03:09 +01:00
|
|
|
return fmt.Errorf("unable to write card to the database, %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteCard deletes a card from the database.
|
|
|
|
// TODO: finish implementation.
|
2023-04-26 08:32:33 +01:00
|
|
|
//func (b *Board) DeleteCard(id int) error {
|
|
|
|
// return nil
|
|
|
|
//}
|