feat: move a card between statuses #3
6 changed files with 272 additions and 22 deletions
83
card.go
Normal file
83
card.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// Card represents a card on a Kanban board.
|
||||
type Card struct {
|
||||
ID int
|
||||
Title string
|
||||
Content string
|
||||
}
|
||||
|
||||
// TODO: this function needs to be unit tested and documented.
|
||||
func createCard(db *bolt.DB, title, content string) error {
|
||||
// first ensure that there is a status list in the database
|
||||
statusList, err := loadAllStatuses(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(statusList) == 0 {
|
||||
return errors.New("the status list cannot be empty")
|
||||
}
|
||||
|
||||
card := Card{
|
||||
ID: -1,
|
||||
Title: title,
|
||||
Content: content,
|
||||
}
|
||||
|
||||
cardID, err := saveCard(db, card)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cardIDs := statusList[0].CardIds
|
||||
|
||||
cardIDs = append(cardIDs, cardID)
|
||||
|
||||
statusList[0].CardIds = cardIDs
|
||||
|
||||
// TODO: change the below to save a single status
|
||||
if err := saveStatuses(db, statusList); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readCard returns a Card value from the database.
|
||||
func readCard(db *bolt.DB, id int) (Card, error) {
|
||||
return loadCard(db, id)
|
||||
}
|
||||
|
||||
// TODO: unit test and document this function.
|
||||
func updateCard(db *bolt.DB, id int, title, content string) error {
|
||||
card, err := loadCard(db, id)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
card.Title = title
|
||||
card.Content = content
|
||||
|
||||
if _, err := saveCard(db, card); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: finish implementation
|
||||
func moveCard(db *bolt.DB, fromStatusID, toStatusID int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: finish implementation
|
||||
func deleteCard(db *bolt.DB, id int) error {
|
||||
return nil
|
||||
}
|
1
go.mod
1
go.mod
|
@ -3,6 +3,7 @@ module forge.dananglin.me.uk/code/dananglin/pelican
|
|||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807
|
||||
github.com/magefile/mage v1.11.0
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1,3 +1,5 @@
|
|||
github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807 h1:jdjd5e68T4R/j4PWxfZqcKY8KtT9oo8IPNVuV4bSXDQ=
|
||||
github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807/go.mod h1:Xoiu5VdKMvbRgHuY7+z64lhu/7lvax/22nzASF6GrO8=
|
||||
github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls=
|
||||
github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
|
|
49
main.go
49
main.go
|
@ -1,4 +1,53 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
path, err := dbPath("")
|
||||
if err != nil {
|
||||
fmt.Printf("Error: Unable to get the database path, %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
db, err := openDatabase(path)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: Unable to open the database, %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
defer db.Close()
|
||||
|
||||
if err := ensureBuckets(db); err != nil {
|
||||
fmt.Printf("Error: Unable to ensure buckets exist, %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var statusList []Status
|
||||
|
||||
statusList, err = readStatuses(db)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: Unable to get status list, %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(statusList) == 0 {
|
||||
newStatusList := newDefaultStatusList()
|
||||
if err := saveStatuses(db, newStatusList); err != nil {
|
||||
fmt.Printf("Error: Unable to save the default status list to the database, %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_, err = readStatuses(db)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: Unable to get status list, %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if err := badUI(db); err != nil {
|
||||
fmt.Printf("Error: %s", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,6 @@ import (
|
|||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// Card represents a card on a Kanban board.
|
||||
type Card struct {
|
||||
ID int
|
||||
Title string
|
||||
Content string
|
||||
}
|
||||
|
||||
// Status represents the status of the Kanban board.
|
||||
type Status struct {
|
||||
ID int
|
||||
|
@ -37,9 +30,7 @@ func (s ByStatusOrder) Less(i, j int) bool {
|
|||
return s[i].Order < s[j].Order
|
||||
}
|
||||
|
||||
// readStatuses reads all statuses from the database. If there are no statuses in the database then the default
|
||||
// status list is created and stored in the database. Before returning the status list, the list is ordered based
|
||||
// on the status' 'Order' field.
|
||||
// readStatuses returns an ordered list of statuses from the database.
|
||||
// TODO: function needs to be unit tested.
|
||||
func readStatuses(db *bolt.DB) ([]Status, error) {
|
||||
statuses, err := loadAllStatuses(db)
|
||||
|
@ -47,34 +38,45 @@ func readStatuses(db *bolt.DB) ([]Status, error) {
|
|||
return statuses, fmt.Errorf("unable to read statuses, %w", err)
|
||||
}
|
||||
|
||||
if len(statuses) == 0 {
|
||||
if err := createDefaultStatusList(db); err != nil {
|
||||
return statuses, fmt.Errorf("unable to create the default statuses, %w", err)
|
||||
}
|
||||
readStatuses(db)
|
||||
}
|
||||
|
||||
sort.Sort(ByStatusOrder(statuses))
|
||||
|
||||
return statuses, nil
|
||||
}
|
||||
|
||||
// createDefaultStatusList creates the default list of statuses and saves the statuses to the database.
|
||||
func createDefaultStatusList(db *bolt.DB) error {
|
||||
s := []Status{
|
||||
func readStatus(db *bolt.DB) (Status, error) {
|
||||
return Status{}, nil
|
||||
}
|
||||
|
||||
func createStatus(db *bolt.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateStatus(db *bolt.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteStatus(db *bolt.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// newDefaultStatusList creates the default list of statuses and saves the statuses to the database.
|
||||
// TODO: function needs to be unit tested.
|
||||
func newDefaultStatusList() []Status {
|
||||
return []Status{
|
||||
{
|
||||
ID: -1,
|
||||
Name: "To Do",
|
||||
Order: 1,
|
||||
},
|
||||
{
|
||||
ID: -1,
|
||||
Name: "Doing",
|
||||
Order: 2,
|
||||
},
|
||||
{
|
||||
ID: -1,
|
||||
Name: "Done",
|
||||
Order: 3,
|
||||
},
|
||||
}
|
||||
|
||||
return saveStatuses(db, s)
|
||||
}
|
113
ui.go
Normal file
113
ui.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
// Right now this will be a very simple, scuffed interface.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"github.com/eiannone/keyboard"
|
||||
)
|
||||
|
||||
func badUI(db *bolt.DB) error {
|
||||
keysEvents, err := keyboard.GetKeys(10)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create the keysEvent channel, %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = keyboard.Close()
|
||||
}()
|
||||
|
||||
// infinite loop
|
||||
fmt.Println(uiUsage())
|
||||
|
||||
app:
|
||||
for {
|
||||
event := <-keysEvents
|
||||
if event.Err != nil {
|
||||
return fmt.Errorf("keys event error: %w", event.Err)
|
||||
}
|
||||
|
||||
switch event.Rune {
|
||||
case 'q':
|
||||
break app
|
||||
case 'r':
|
||||
if err := uiRefreshScuffedBoard(db); err != nil {
|
||||
fmt.Printf("Error: Unable to refresh board, %s\n", err)
|
||||
}
|
||||
case 'a':
|
||||
if err := uiCreateCard(db); err != nil {
|
||||
fmt.Printf("Error: Unable to add a card, %s\n", err)
|
||||
}
|
||||
case 'v':
|
||||
if err := uiViewCard(db, 1); err != nil {
|
||||
fmt.Printf("Error: Unable to view card, %s\n", err)
|
||||
}
|
||||
default:
|
||||
fmt.Println("Error: Unknown key event.")
|
||||
}
|
||||
}
|
||||
|
||||
// wait for a keyboard press
|
||||
// take action
|
||||
// a to add a card
|
||||
// u to update a card
|
||||
return nil
|
||||
}
|
||||
|
||||
func uiRefreshScuffedBoard(db *bolt.DB) error {
|
||||
statusList, err := readStatuses(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("--------------------\n")
|
||||
for _, s := range statusList{
|
||||
fmt.Printf("Status ID: %d\nStatus Name: \"%s\"\nCard IDs: %v\n\n", s.ID, s.Name, s.CardIds)
|
||||
}
|
||||
fmt.Printf("--------------------\n\n\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func uiCreateCard(db *bolt.DB) error {
|
||||
title := "A card title"
|
||||
|
||||
content := "As a user, this is a ticket for me.\nAs a user, I want to close it."
|
||||
|
||||
if err := createCard(db, title, content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Sample card created successfully.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func uiViewCard(db *bolt.DB, id int) error {
|
||||
card, err := readCard(db, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
fmt.Printf("====================\n")
|
||||
fmt.Printf("[%d] %s\n", card.ID, card.Title)
|
||||
fmt.Printf("--------------------\n")
|
||||
fmt.Println(card.Content)
|
||||
fmt.Printf("====================\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func uiUsage() string {
|
||||
usage := `
|
||||
Press 'q' to quit
|
||||
Press 'r' to refresh the board
|
||||
Press 'a' to add a sample card
|
||||
Press 'v' to view the first sample card
|
||||
|
||||
`
|
||||
|
||||
return usage
|
||||
}
|
Loading…
Reference in a new issue