diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 8f175aa..dfd1704 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,6 +1,6 @@ #!/bin/bash -echo "Backend linting" +echo "Linting" golangci-lint run if [ $? -ne 0 ]; then echo "golangci-lint failed. Please fix the errors before committing." diff --git a/db/queries/message.sql b/db/queries/message.sql index 727583e..5617dd1 100644 --- a/db/queries/message.sql +++ b/db/queries/message.sql @@ -23,3 +23,19 @@ RETURNING *; -- name: DeleteMessage :execrows DELETE FROM message WHERE id = ?; + + +-- Other + + +-- name: GetLastMessage :one +SELECT * +FROM message +ORDER BY id DESC +LIMIT 1; + +-- name: GetMessageSinceID :many +SELECT * +FROM message +WHERE id > ? +ORDER BY created_at ASC; diff --git a/db/queries/tap.sql b/db/queries/tap.sql index 2ffb924..36072da 100644 --- a/db/queries/tap.sql +++ b/db/queries/tap.sql @@ -24,8 +24,10 @@ RETURNING *; DELETE FROM tap WHERE id = ?; + -- Other + -- name: GetTapByOrderID :one SELECT * FROM tap diff --git a/internal/cmd/tui.go b/internal/cmd/tui.go index cdcdf21..2ad8ad6 100644 --- a/internal/cmd/tui.go +++ b/internal/cmd/tui.go @@ -32,7 +32,7 @@ func TUI(db *db.DB) (*tea.Program, error) { tui := tui.New(val(db)) - program := tea.NewProgram(tui) + program := tea.NewProgram(tui, tea.WithAltScreen()) return program, nil } diff --git a/internal/pkg/db/sqlc/message.sql.go b/internal/pkg/db/sqlc/message.sql.go index 24b2306..14626f9 100644 --- a/internal/pkg/db/sqlc/message.sql.go +++ b/internal/pkg/db/sqlc/message.sql.go @@ -83,6 +83,29 @@ func (q *Queries) GetAllMessages(ctx context.Context) ([]Message, error) { return items, nil } +const getLastMessage = `-- name: GetLastMessage :one + + +SELECT id, name, ip, message, created_at +FROM message +ORDER BY id DESC +LIMIT 1 +` + +// Other +func (q *Queries) GetLastMessage(ctx context.Context) (Message, error) { + row := q.db.QueryRowContext(ctx, getLastMessage) + var i Message + err := row.Scan( + &i.ID, + &i.Name, + &i.Ip, + &i.Message, + &i.CreatedAt, + ) + return i, err +} + const getMessageByID = `-- name: GetMessageByID :one SELECT id, name, ip, message, created_at FROM message @@ -102,6 +125,42 @@ func (q *Queries) GetMessageByID(ctx context.Context, id int64) (Message, error) return i, err } +const getMessageSinceID = `-- name: GetMessageSinceID :many +SELECT id, name, ip, message, created_at +FROM message +WHERE id > ? +ORDER BY created_at ASC +` + +func (q *Queries) GetMessageSinceID(ctx context.Context, id int64) ([]Message, error) { + rows, err := q.db.QueryContext(ctx, getMessageSinceID, id) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Message + for rows.Next() { + var i Message + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Ip, + &i.Message, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const updateMessage = `-- name: UpdateMessage :one UPDATE message SET name = ?, ip = ?, message = ? diff --git a/internal/pkg/db/sqlc/tap.sql.go b/internal/pkg/db/sqlc/tap.sql.go index 775c7ab..66dfd70 100644 --- a/internal/pkg/db/sqlc/tap.sql.go +++ b/internal/pkg/db/sqlc/tap.sql.go @@ -240,6 +240,7 @@ func (q *Queries) GetTapByID(ctx context.Context, id int64) (Tap, error) { const getTapByOrderID = `-- name: GetTapByOrderID :one + SELECT id, order_id, order_created_at, name, category, created_at FROM tap WHERE order_id = ? diff --git a/ui/screen/cammie.go b/ui/screen/cammie.go index 8c96472..0a2e97b 100644 --- a/ui/screen/cammie.go +++ b/ui/screen/cammie.go @@ -4,29 +4,34 @@ package screen import ( tea "github.com/charmbracelet/bubbletea" "github.com/zeusWPI/scc/internal/pkg/db" + "github.com/zeusWPI/scc/ui/view" ) // Cammie represents the cammie screen type Cammie struct { - db *db.DB + db *db.DB + cammie *view.MessageModel } // NewCammie creates a new cammie screen func NewCammie(db *db.DB) tea.Model { - return &Cammie{db: db} + return &Cammie{db: db, cammie: view.NewMessageModel(db)} } // Init initializes the cammie screen func (c *Cammie) Init() tea.Cmd { - return nil + return c.cammie.Init() } // Update updates the cammie screen -func (c *Cammie) Update(_ tea.Msg) (tea.Model, tea.Cmd) { - return c, nil +func (c *Cammie) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + cammie, cmd := c.cammie.Update(msg) + c.cammie = cammie.(*view.MessageModel) + + return c, cmd } // View returns the cammie screen view func (c *Cammie) View() string { - return "" + return c.cammie.View() } diff --git a/ui/tui.go b/ui/tui.go index 146dc90..ae39bdf 100644 --- a/ui/tui.go +++ b/ui/tui.go @@ -18,7 +18,7 @@ func New(screen tea.Model) *TUI { // Init initializes the tui func (t *TUI) Init() tea.Cmd { - return tea.Batch(tea.EnterAltScreen, t.screen.Init()) + return tea.Batch(t.screen.Init()) } // Update updates the tui diff --git a/ui/view/message.go b/ui/view/message.go new file mode 100644 index 0000000..80e1762 --- /dev/null +++ b/ui/view/message.go @@ -0,0 +1,109 @@ +package view + +import ( + "context" + "fmt" + "hash/fnv" + "time" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/list" + "github.com/zeusWPI/scc/internal/pkg/db" + "github.com/zeusWPI/scc/internal/pkg/db/sqlc" + "go.uber.org/zap" +) + +// MessageModel represents the model for the message view +type MessageModel struct { + db *db.DB + lastMessageID int64 + messages []string +} + +// MessageMsg represents the message to update the message view +type MessageMsg struct { + lastMessageID int64 + messages []string +} + +var messageColor = []string{ + "#800000", "#008000", "#808000", "#000080", "#800080", "#008080", "#c0c0c0", + "#ff0000", "#00ff00", "#ffff00", "#0000ff", "#ff00ff", "#00ffff", "#ffffff", +} + +// NewMessageModel creates a new message model view +func NewMessageModel(db *db.DB) *MessageModel { + return &MessageModel{db: db, lastMessageID: -1, messages: []string{}} +} + +// Init initializes the message model view +func (c *MessageModel) Init() tea.Cmd { + return updateMessages(c.db, c.lastMessageID) +} + +// Update updates the message model view +func (c *MessageModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case MessageMsg: + c.lastMessageID = msg.lastMessageID + c.messages = append(c.messages, msg.messages...) + + return c, updateMessages(c.db, c.lastMessageID) + } + + return c, nil +} + +// View returns the view for the message model +func (c *MessageModel) View() string { + l := list.New(c.messages).Enumerator(func(_ list.Items, _ int) string { return "" }) + return l.String() +} + +func updateMessages(db *db.DB, lastMessageID int64) tea.Cmd { + return tea.Tick(1*time.Second, func(_ time.Time) tea.Msg { + message, err := db.Queries.GetLastMessage(context.Background()) + if err != nil { + zap.S().Error("DB: Failed to get last message", err) + return MessageMsg{lastMessageID: lastMessageID, messages: []string{}} + } + + if message.ID <= lastMessageID { + return MessageMsg{lastMessageID: lastMessageID, messages: []string{}} + } + + messages, err := db.Queries.GetMessageSinceID(context.Background(), lastMessageID) + if err != nil { + zap.S().Error("DB: Failed to get messages", err) + return MessageMsg{lastMessageID: lastMessageID, messages: []string{}} + } + + formattedMessages := make([]string, 0, len(messages)) + for _, message := range messages { + formattedMessages = append(formattedMessages, formatMessage(message)) + } + + return MessageMsg{lastMessageID: message.ID, messages: formattedMessages} + }) +} + +func hashColor(s string) string { + h := fnv.New32a() + h.Write([]byte(s)) + hash := h.Sum32() + return messageColor[hash%uint32(len(messageColor))] +} + +func formatMessage(msg sqlc.Message) string { + dateStyle := lipgloss.NewStyle().Faint(true) + date := dateStyle.Render(fmt.Sprintf("%s | ", msg.CreatedAt.Format("02/01"))) + + color := hashColor(msg.Name) + colorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(color)) + + sender := fmt.Sprintf("%s %s %s ", colorStyle.Render("["), colorStyle.Bold(true).Render(msg.Name), colorStyle.Render("]")) + message := colorStyle.Render(msg.Message) + + return fmt.Sprintf("%s%s%s", date, sender, message) +} diff --git a/ui/view/tap.go b/ui/view/tap.go index e654030..486fc50 100644 --- a/ui/view/tap.go +++ b/ui/view/tap.go @@ -22,8 +22,8 @@ type TapModel struct { food float64 } -// TapMessage represents a tap message -type TapMessage struct { +// TapMsg represents a tap message +type TapMsg struct { lastOrderID int64 items []tapItem } @@ -53,7 +53,7 @@ func (t *TapModel) Init() tea.Cmd { // Update updates the tap model func (t *TapModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { - case TapMessage: + case TapMsg: t.lastOrderID = msg.lastOrderID for _, msg := range msg.items { @@ -123,17 +123,17 @@ func updateOrders(db *db.DB, lastOrderID int64) tea.Cmd { order, err := db.Queries.GetLastOrderByOrderID(context.Background()) if err != nil { zap.S().Error("DB: Failed to get last order", err) - return TapMessage{lastOrderID: lastOrderID, items: nil} + return TapMsg{lastOrderID: lastOrderID, items: nil} } if order.OrderID <= lastOrderID { - return TapMessage{lastOrderID: lastOrderID, items: nil} + return TapMsg{lastOrderID: lastOrderID, items: nil} } orders, err := db.Queries.GetOrderCountByCategorySinceOrderID(context.Background(), lastOrderID) if err != nil { zap.S().Error("DB: Failed to get tap orders", err) - return TapMessage{lastOrderID: lastOrderID, items: nil} + return TapMsg{lastOrderID: lastOrderID, items: nil} } mate, soft, beer, food := 0.0, 0.0, 0.0, 0.0 @@ -164,6 +164,6 @@ func updateOrders(db *db.DB, lastOrderID int64) tea.Cmd { messages = append(messages, tapItem{"Food", food}) } - return TapMessage{lastOrderID: order.OrderID, items: messages} + return TapMsg{lastOrderID: order.OrderID, items: messages} }) }