package sqlite import ( "context" "database/sql" "fmt" "github.com/abelith/spellbook/internal/models" "strings" ) const schema = ` CREATE TABLE IF NOT EXISTS spells ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, spell TEXT NOT NULL, short_desc TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', tags TEXT NOT NULL DEFAULT '' ); ` type scanner interface { Scan(dst ...any) error } type Repo struct { db *sql.DB } func NewRepo(db *sql.DB) *Repo { return &Repo{db: db} } func (r *Repo) Init(ctx context.Context) error { if r.db == nil { return fmt.Errorf("no db provided") } if _, err := r.db.ExecContext(ctx, schema); err != nil { return err } return nil } func (r *Repo) Add(ctx context.Context, spell models.Spell) error { const query = `INSERT INTO spells (name, spell, short_desc, description, tags) VALUES (?, ?, ?, ?, ?)` tags := strings.Join(spell.Tags, ",") _, err := r.db.ExecContext( ctx, query, spell.Name, spell.Spell, spell.ShortDesc, spell.Description, tags, ) if err != nil { return err } return nil } func (r *Repo) Delete(ctx context.Context, spellName string) error { const query = `DELETE FROM spells WHERE name = ?` if _, err := r.db.ExecContext(ctx, query, spellName); err != nil { return err } return nil } func (r *Repo) Get(ctx context.Context, spellName string) (models.Spell, error) { const query = `SELECT id, name, spell, short_desc, description, tags FROM spells WHERE name = ?` row := r.db.QueryRowContext(ctx, query, spellName) if err := row.Err(); err != nil { return models.Spell{}, err } return scanSpell(row) } func (r *Repo) GetMany(ctx context.Context, query models.SearchQuery) ([]models.Spell, error) { sqlQuery := ` SELECT id, name, spell, short_desc, description, tags FROM spells WHERE id > ?` args := []any{query.Cursor} for _, tag := range query.Tags { if trimmed := strings.TrimSpace(tag); trimmed != "" { sqlQuery += " AND tags LIKE ?" args = append(args, "%"+trimmed+"%") } } sqlQuery += " ORDER BY id ASC LIMIT ?" limit := query.Limit if limit <= 0 { limit = 20 } args = append(args, limit) rows, err := r.db.QueryContext(ctx, sqlQuery, args...) if err != nil { return nil, err } spells := make([]models.Spell, 0) for rows.Next() { spell, err := scanSpell(rows) if err != nil { return nil, err } spells = append(spells, spell) } if err := rows.Err(); err != nil { return nil, err } return spells, nil } func scanSpell(row scanner) (models.Spell, error) { spell := models.Spell{} tagsString := "" if err := row.Scan(&spell.Id, &spell.Name, &spell.Spell, &spell.ShortDesc, &spell.Description, &tagsString); err != nil { return models.Spell{}, err } if tagsString != "" { spell.Tags = strings.Split(tagsString, ",") } return spell, nil }