Files
havenllo/internal/store/sqlite.go
Hermes afd23ca751
Some checks failed
Build and deploy Havenllo / Verify Go and frontend (push) Failing after 29s
Build and deploy Havenllo / Build and push image (push) Has been skipped
Build and deploy Havenllo / Apply and restart Havenllo (push) Has been skipped
ui overhaul
2026-07-14 11:09:54 -03:00

144 lines
4.0 KiB
Go

// Package store contains small, parameterized SQLite queries used by the service.
package store
import (
"context"
"database/sql"
"fmt"
"havenllo/internal/domain"
)
type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
}
func Lists(ctx context.Context, q DBTX, counts bool) ([]domain.List, error) {
query := `SELECT l.id, l.name, l.position`
if counts {
query += `, COUNT(c.id) FROM lists l LEFT JOIN cards c ON c.list_id = l.id GROUP BY l.id, l.name, l.position ORDER BY l.position, l.id`
} else {
query += ` FROM lists l ORDER BY l.position, l.id`
}
rows, err := q.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
lists := make([]domain.List, 0)
for rows.Next() {
var list domain.List
if counts {
err = rows.Scan(&list.ID, &list.Name, &list.Position, &list.CardCount)
} else {
err = rows.Scan(&list.ID, &list.Name, &list.Position)
}
if err != nil {
return nil, err
}
lists = append(lists, list)
}
return lists, rows.Err()
}
func List(ctx context.Context, q DBTX, id int64) (domain.List, error) {
var list domain.List
err := q.QueryRowContext(ctx, "SELECT id, name, position FROM lists WHERE id = ?", id).Scan(&list.ID, &list.Name, &list.Position)
if err == sql.ErrNoRows {
return domain.List{}, domain.ErrNotFound
}
return list, err
}
func Cards(ctx context.Context, q DBTX, listID int64) ([]domain.Card, error) {
rows, err := q.QueryContext(ctx, `SELECT id, list_id, title, description, position, created_at, updated_at
FROM cards WHERE list_id = ? ORDER BY position, id`, listID)
if err != nil {
return nil, err
}
defer rows.Close()
cards := make([]domain.Card, 0)
for rows.Next() {
card, err := scanCard(rows)
if err != nil {
return nil, err
}
cards = append(cards, card)
}
return cards, rows.Err()
}
func Card(ctx context.Context, q DBTX, id int64) (domain.Card, error) {
row := q.QueryRowContext(ctx, `SELECT id, list_id, title, description, position, created_at, updated_at
FROM cards WHERE id = ?`, id)
card, err := scanCard(row)
if err == sql.ErrNoRows {
return domain.Card{}, domain.ErrNotFound
}
return card, err
}
type scanner interface{ Scan(...any) error }
func scanCard(row scanner) (domain.Card, error) {
var card domain.Card
return card, row.Scan(&card.ID, &card.ListID, &card.Title, &card.Description, &card.Position, &card.CreatedAt, &card.UpdatedAt)
}
func Snapshot(ctx context.Context, q DBTX) (domain.Snapshot, error) {
lists, err := Lists(ctx, q, false)
if err != nil {
return domain.Snapshot{}, err
}
snapshot := domain.Snapshot{Lists: make([]domain.ListWithCards, 0, len(lists))}
for _, list := range lists {
cards, err := Cards(ctx, q, list.ID)
if err != nil {
return domain.Snapshot{}, err
}
snapshot.Lists = append(snapshot.Lists, domain.ListWithCards{ID: list.ID, Name: list.Name, Position: list.Position, Cards: cards})
}
return snapshot, nil
}
func MaxListPosition(ctx context.Context, q DBTX) (float64, error) {
var position float64
err := q.QueryRowContext(ctx, "SELECT COALESCE(MAX(position), 0) FROM lists").Scan(&position)
return position, err
}
func MaxCardPosition(ctx context.Context, q DBTX, listID int64) (float64, error) {
var position float64
err := q.QueryRowContext(ctx, "SELECT COALESCE(MAX(position), 0) FROM cards WHERE list_id = ?", listID).Scan(&position)
return position, err
}
func CountLists(ctx context.Context, q DBTX) (int, error) {
var count int
err := q.QueryRowContext(ctx, "SELECT COUNT(*) FROM lists").Scan(&count)
return count, err
}
func MustRows(result sql.Result, err error) error {
if err != nil {
return err
}
n, err := result.RowsAffected()
if err != nil {
return err
}
if n == 0 {
return domain.ErrNotFound
}
return nil
}
func Wrap(action string, err error) error {
if err == nil || err == domain.ErrNotFound {
return err
}
return fmt.Errorf("%s: %w", action, err)
}