WIP: UI improvements and code refactoring #2

Closed
dananglin wants to merge 25 commits from ui-overhall into main
4 changed files with 73 additions and 67 deletions
Showing only changes of commit b07ce3f415 - Show all commits

View file

@ -10,16 +10,25 @@ import (
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
) )
// OpenProject reads the project from the database. If no board exists then a new one will be created. type Board struct {
func OpenProject(path string) (*bolt.DB, error) { db *bolt.DB
db, err := database.OpenDatabase(path)
if err != nil {
return nil, fmt.Errorf("unable to open the database, %w", err)
} }
statusList, err := ReadStatusList(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) {
db, err := database.OpenDatabase(path)
if err != nil { if err != nil {
return nil, err return Board{}, fmt.Errorf("unable to open the database, %w", err)
}
board := Board{
db: db,
}
statusList, err := board.StatusList()
if err != nil {
return Board{}, err
} }
if len(statusList) == 0 { if len(statusList) == 0 {
@ -32,16 +41,21 @@ func OpenProject(path string) (*bolt.DB, error) {
} }
if _, err := database.WriteMany(db, database.StatusBucket, boltItems); err != nil { if _, err := database.WriteMany(db, database.StatusBucket, boltItems); err != nil {
return nil, fmt.Errorf("unable to save the default status list to the database, %w", err) return Board{}, fmt.Errorf("unable to save the default status list to the database, %w", err)
} }
} }
return db, nil
return board, nil
} }
// ReadStatusList returns the ordered list of statuses from the database. func (b *Board) Close() error {
func ReadStatusList(db *bolt.DB) ([]Status, error) { return b.db.Close()
data, err := database.ReadAll(db, database.StatusBucket) }
// StatusList returns the ordered list of statuses from the database.
func (b *Board) StatusList() ([]Status, error) {
data, err := database.ReadAll(b.db, database.StatusBucket)
if err != nil { if err != nil {
return []Status{}, fmt.Errorf("unable to read the status list, %w", err) return []Status{}, fmt.Errorf("unable to read the status list, %w", err)
} }
@ -68,22 +82,22 @@ func ReadStatusList(db *bolt.DB) ([]Status, error) {
} }
// TODO: Finish implementation. // TODO: Finish implementation.
func ReadStatus(db *bolt.DB) (Status, error) { func (b *Board) ReadStatus() (Status, error) {
return Status{}, nil return Status{}, nil
} }
// TODO: Finish implementation. // TODO: Finish implementation.
func CreateStatus(db *bolt.DB) error { func (b *Board) NewStatus() error {
return nil return nil
} }
// TODO: Finish implementation. // TODO: Finish implementation.
func UpdateStatus(db *bolt.DB) error { func (b *Board) UpdateStatus() error {
return nil return nil
} }
// TODO: Finish implementation. // TODO: Finish implementation.
func DeleteStatus(db *bolt.DB) error { func (b *Board) DeleteStatus() error {
return nil return nil
} }
@ -93,8 +107,8 @@ type CardArgs struct {
} }
// CreateCard creates a card in the database. // CreateCard creates a card in the database.
func CreateCard(db *bolt.DB, args CardArgs) error { func (b *Board) CreateCard(args CardArgs) error {
statusList, err := ReadStatusList(db) statusList, err := b.StatusList()
if err != nil { if err != nil {
return fmt.Errorf("unable to read the status list, %w", err) return fmt.Errorf("unable to read the status list, %w", err)
} }
@ -109,7 +123,7 @@ func CreateCard(db *bolt.DB, args CardArgs) error {
Content: args.NewContent, Content: args.NewContent,
} }
cardID, err := database.Write(db, database.CardBucket, &card) cardID, err := database.Write(b.db, database.CardBucket, &card)
if err != nil { if err != nil {
return fmt.Errorf("unable to write card to the database, %w", err) return fmt.Errorf("unable to write card to the database, %w", err)
} }
@ -118,16 +132,16 @@ func CreateCard(db *bolt.DB, args CardArgs) error {
initialStatus.AddCardID(cardID) initialStatus.AddCardID(cardID)
if _, err := database.Write(db, database.StatusBucket, &initialStatus); err != nil { if _, err := database.Write(b.db, database.StatusBucket, &initialStatus); err != nil {
return fmt.Errorf("unable to write the %s status to the database, %w", initialStatus.Name, err) return fmt.Errorf("unable to write the %s status to the database, %w", initialStatus.Name, err)
} }
return nil return nil
} }
// ReadCard returns a Card value from the database. // Card returns a Card value from the database.
func ReadCard(db *bolt.DB, id int) (Card, error) { func (b *Board) Card(id int) (Card, error) {
data, err := database.Read(db, database.CardBucket, id) data, err := database.Read(b.db, database.CardBucket, id)
if err != nil { if err != nil {
return Card{}, fmt.Errorf("unable to read card [%d] from the database, %w", id, err) return Card{}, fmt.Errorf("unable to read card [%d] from the database, %w", id, err)
} }
@ -145,10 +159,10 @@ func ReadCard(db *bolt.DB, id int) (Card, error) {
return card, nil return card, nil
} }
// ReadCardList returns a list of Card values from the database. // CardList returns a list of Card values from the database.
// TODO: function needs testing. // TODO: function needs testing.
func ReadCardList(db *bolt.DB, ids []int) ([]Card, error) { func (b *Board) CardList(ids []int) ([]Card, error) {
data, err := database.ReadMany(db, database.CardBucket, ids) data, err := database.ReadMany(b.db, database.CardBucket, ids)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to read card list from the database, %w", err) return nil, fmt.Errorf("unable to read card list from the database, %w", err)
} }
@ -178,8 +192,8 @@ type UpdateCardArgs struct {
} }
// UpdateCard modifies an existing card in the database. // UpdateCard modifies an existing card in the database.
func UpdateCard(db *bolt.DB, args UpdateCardArgs) error { func (b *Board) UpdateCard(args UpdateCardArgs) error {
card, err := ReadCard(db, args.CardID) card, err := b.Card(args.CardID)
if err != nil { if err != nil {
return err return err
} }
@ -192,7 +206,7 @@ func UpdateCard(db *bolt.DB, args UpdateCardArgs) error {
card.Content = args.NewContent card.Content = args.NewContent
} }
if _, err := database.Write(db, database.CardBucket, &card); err != nil { if _, err := database.Write(b.db, database.CardBucket, &card); err != nil {
return fmt.Errorf("unable to write card to the database, %w", err) return fmt.Errorf("unable to write card to the database, %w", err)
} }
@ -207,12 +221,12 @@ type UpdateCardStatusArgs struct {
// UpdateCardStatus moves a card between statuses. // UpdateCardStatus moves a card between statuses.
// TODO: finish implementation. // TODO: finish implementation.
func UpdateCardStatus(db *bolt.DB, args UpdateCardStatusArgs) error { func (b *Board) UpdateCardStatus(args UpdateCardStatusArgs) error {
return nil return nil
} }
// DeleteCard deletes a card from the database. // DeleteCard deletes a card from the database.
// TODO: finish implementation. // TODO: finish implementation.
func DeleteCard(db *bolt.DB, id int) error { func (b *Board) DeleteCard(id int) error {
return nil return nil
} }

View file

@ -8,7 +8,6 @@ import (
"testing" "testing"
"codeflow.dananglin.me.uk/apollo/canal/internal/board" "codeflow.dananglin.me.uk/apollo/canal/internal/board"
bolt "go.etcd.io/bbolt"
) )
func TestCardLifecycle(t *testing.T) { func TestCardLifecycle(t *testing.T) {
@ -22,33 +21,33 @@ func TestCardLifecycle(t *testing.T) {
testDBPath := filepath.Join(projectDir, "test", "databases", "Board_TestCardLifecycle.db") testDBPath := filepath.Join(projectDir, "test", "databases", "Board_TestCardLifecycle.db")
os.Remove(testDBPath) os.Remove(testDBPath)
db, err := board.OpenProject(testDBPath) b, err := board.Open(testDBPath)
if err != nil { if err != nil {
t.Fatalf("Unable to open the test database %s, %s.", testDBPath, err) t.Fatalf("Unable to open the test database %s, %s.", testDBPath, err)
} }
defer func() { defer func() {
_ = db.Close() _ = b.Close()
}() }()
initialCardTitle := "A test card." initialCardTitle := "A test card."
initialCardContent := "Ensure that this card is safely stored in the database." initialCardContent := "Ensure that this card is safely stored in the database."
expectedCardID := 1 expectedCardID := 1
testCreateCard(t, db, initialCardTitle, initialCardContent, expectedCardID) testCreateCard(t, b, initialCardTitle, initialCardContent, expectedCardID)
testReadCard(t, db, expectedCardID, initialCardTitle, initialCardContent) testReadCard(t, b, expectedCardID, initialCardTitle, initialCardContent)
modifiedCardTitle := "Test card updated." modifiedCardTitle := "Test card updated."
modifiedCardContent1 := "Ensure that this card is safely updated in the database." modifiedCardContent1 := "Ensure that this card is safely updated in the database."
testUpdateCard(t, db, expectedCardID, modifiedCardTitle, modifiedCardContent1) testUpdateCard(t, b, expectedCardID, modifiedCardTitle, modifiedCardContent1)
modifiedCardContent2 := "Updated card content only." modifiedCardContent2 := "Updated card content only."
testUpdateCardContent(t, db, expectedCardID, modifiedCardTitle, modifiedCardContent2) testUpdateCardContent(t, b, expectedCardID, modifiedCardTitle, modifiedCardContent2)
} }
func testCreateCard(t *testing.T, db *bolt.DB, title, content string, wantID int) { func testCreateCard(t *testing.T, b board.Board, title, content string, wantID int) {
t.Helper() t.Helper()
args := board.CardArgs{ args := board.CardArgs{
@ -56,11 +55,11 @@ func testCreateCard(t *testing.T, db *bolt.DB, title, content string, wantID int
NewContent: content, NewContent: content,
} }
if err := board.CreateCard(db, args); err != nil { if err := b.CreateCard(args); err != nil {
t.Fatalf("Unable to create the test card, %s.", err) t.Fatalf("Unable to create the test card, %s.", err)
} }
statusList, err := board.ReadStatusList(db) statusList, err := b.StatusList()
if err != nil { if err != nil {
t.Fatalf("Unable to run `ReadStatusList`, %s.", err) t.Fatalf("Unable to run `ReadStatusList`, %s.", err)
} }
@ -82,10 +81,10 @@ func testCreateCard(t *testing.T, db *bolt.DB, title, content string, wantID int
} }
} }
func testReadCard(t *testing.T, db *bolt.DB, cardID int, wantTitle, wantContent string) { func testReadCard(t *testing.T, b board.Board, cardID int, wantTitle, wantContent string) {
t.Helper() t.Helper()
card, err := board.ReadCard(db, cardID) card, err := b.Card(cardID)
if err != nil { if err != nil {
t.Fatalf("Unable to read test card, %s.", err) t.Fatalf("Unable to read test card, %s.", err)
} }
@ -103,7 +102,7 @@ func testReadCard(t *testing.T, db *bolt.DB, cardID int, wantTitle, wantContent
} }
} }
func testUpdateCard(t *testing.T, db *bolt.DB, cardID int, newTitle, newContent string) { func testUpdateCard(t *testing.T, b board.Board, cardID int, newTitle, newContent string) {
t.Helper() t.Helper()
args := board.UpdateCardArgs{ args := board.UpdateCardArgs{
@ -114,11 +113,11 @@ func testUpdateCard(t *testing.T, db *bolt.DB, cardID int, newTitle, newContent
}, },
} }
if err := board.UpdateCard(db, args); err != nil { if err := b.UpdateCard(args); err != nil {
t.Fatalf("Unable to update the test card, %s", err) t.Fatalf("Unable to update the test card, %s", err)
} }
got, err := board.ReadCard(db, cardID) got, err := b.Card(cardID)
if err != nil { if err != nil {
t.Fatalf("Unable to read the modified test card, %s", err) t.Fatalf("Unable to read the modified test card, %s", err)
} }
@ -136,7 +135,7 @@ func testUpdateCard(t *testing.T, db *bolt.DB, cardID int, newTitle, newContent
} }
} }
func testUpdateCardContent(t *testing.T, db *bolt.DB, cardID int, expectedTitle, newContent string) { func testUpdateCardContent(t *testing.T, b board.Board, cardID int, expectedTitle, newContent string) {
t.Helper() t.Helper()
args := board.UpdateCardArgs{ args := board.UpdateCardArgs{
@ -147,11 +146,11 @@ func testUpdateCardContent(t *testing.T, db *bolt.DB, cardID int, expectedTitle,
}, },
} }
if err := board.UpdateCard(db, args); err != nil { if err := b.UpdateCard(args); err != nil {
t.Fatalf("Unable to update the test card, %s", err) t.Fatalf("Unable to update the test card, %s", err)
} }
got, err := board.ReadCard(db, cardID) got, err := b.Card(cardID)
if err != nil { if err != nil {
t.Fatalf("Unable to read the modified test card, %s", err) t.Fatalf("Unable to read the modified test card, %s", err)
} }

View file

@ -5,7 +5,6 @@ import (
"codeflow.dananglin.me.uk/apollo/canal/internal/board" "codeflow.dananglin.me.uk/apollo/canal/internal/board"
"github.com/rivo/tview" "github.com/rivo/tview"
bolt "go.etcd.io/bbolt"
) )
type shiftDirection int type shiftDirection int
@ -29,34 +28,28 @@ type App struct {
flex *tview.Flex flex *tview.Flex
pages *tview.Pages pages *tview.Pages
focusedColumn int focusedColumn int
db *bolt.DB board board.Board
} }
// shutdown shuts down the application. // shutdown shuts down the application.
func (a *App) shutdown() { func (a *App) shutdown() {
a.closeDB() a.closeBoard()
a.Stop() a.Stop()
} }
// closeDB closes the BoltDB database. // closeBoard closes the BoltDB database.
func (a *App) closeDB() { func (a *App) closeBoard() {
if a.db != nil { _ = a.board.Close()
_ = a.db.Close()
}
} }
// openProject opens the kanban project. // openBoard opens the kanban project.
func (a *App) openProject(path string) error { func (a *App) openBoard(path string) error {
if a.db != nil && len(a.db.Path()) > 0 { b, err := board.Open(path)
a.db.Close()
}
db, err := board.OpenProject(path)
if err != nil { if err != nil {
return fmt.Errorf("unable to load board, %w", err) return fmt.Errorf("unable to load board, %w", err)
} }
a.db = db a.board = b
if err = a.refresh(); err != nil { if err = a.refresh(); err != nil {
return err return err
@ -67,7 +60,7 @@ func (a *App) openProject(path string) error {
// refresh refreshes the UI. // refresh refreshes the UI.
func (a *App) refresh() error { func (a *App) refresh() error {
statusList, err := board.ReadStatusList(a.db) statusList, err := a.board.StatusList()
if err != nil { if err != nil {
return fmt.Errorf("unable to get the status list, %w", err) return fmt.Errorf("unable to get the status list, %w", err)
} }
@ -88,7 +81,7 @@ func (a *App) updateBoard(statusList []board.Status) error {
columns[i] = a.newColumn(statusList[i].ID, statusList[i].Name) columns[i] = a.newColumn(statusList[i].ID, statusList[i].Name)
if len(statusList[i].CardIds) > 0 { if len(statusList[i].CardIds) > 0 {
cards, err := board.ReadCardList(a.db, statusList[i].CardIds) cards, err := a.board.CardList(statusList[i].CardIds)
if err != nil { if err != nil {
return fmt.Errorf("unable to get the card list. %w", err) return fmt.Errorf("unable to get the card list. %w", err)
} }
@ -136,7 +129,7 @@ func (a *App) newCard(title, content string) error {
NewContent: content, NewContent: content,
} }
if err := board.CreateCard(a.db, args); err != nil { if err := a.board.CreateCard(args); err != nil {
return fmt.Errorf("unable to create card, %w", err) return fmt.Errorf("unable to create card, %w", err)
} }

View file

@ -34,7 +34,7 @@ func initApp(a *App) {
a.pages.ShowPage(quitPage) a.pages.ShowPage(quitPage)
a.SetFocus(quit) a.SetFocus(quit)
} else if event.Rune() == 'o' { } else if event.Rune() == 'o' {
a.openProject("") a.openBoard("")
} else if event.Rune() == 'a' { } else if event.Rune() == 'a' {
a.pages.ShowPage(addPage) a.pages.ShowPage(addPage)
a.SetFocus(add) a.SetFocus(add)