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
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807
|
||||||
github.com/magefile/mage v1.11.0
|
github.com/magefile/mage v1.11.0
|
||||||
go.etcd.io/bbolt v1.3.6
|
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 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls=
|
||||||
github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||||
|
|
49
main.go
49
main.go
|
@ -1,4 +1,53 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
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"
|
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.
|
// Status represents the status of the Kanban board.
|
||||||
type Status struct {
|
type Status struct {
|
||||||
ID int
|
ID int
|
||||||
|
@ -37,9 +30,7 @@ func (s ByStatusOrder) Less(i, j int) bool {
|
||||||
return s[i].Order < s[j].Order
|
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
|
// readStatuses returns an ordered list of statuses from the database.
|
||||||
// status list is created and stored in the database. Before returning the status list, the list is ordered based
|
|
||||||
// on the status' 'Order' field.
|
|
||||||
// TODO: function needs to be unit tested.
|
// TODO: function needs to be unit tested.
|
||||||
func readStatuses(db *bolt.DB) ([]Status, error) {
|
func readStatuses(db *bolt.DB) ([]Status, error) {
|
||||||
statuses, err := loadAllStatuses(db)
|
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)
|
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))
|
sort.Sort(ByStatusOrder(statuses))
|
||||||
|
|
||||||
return statuses, nil
|
return statuses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// createDefaultStatusList creates the default list of statuses and saves the statuses to the database.
|
func readStatus(db *bolt.DB) (Status, error) {
|
||||||
func createDefaultStatusList(db *bolt.DB) error {
|
return Status{}, nil
|
||||||
s := []Status{
|
}
|
||||||
|
|
||||||
|
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",
|
Name: "To Do",
|
||||||
Order: 1,
|
Order: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
ID: -1,
|
||||||
Name: "Doing",
|
Name: "Doing",
|
||||||
Order: 2,
|
Order: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
ID: -1,
|
||||||
Name: "Done",
|
Name: "Done",
|
||||||
Order: 3,
|
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