package sqlite import ( "context" "database/sql" sq "github.com/Masterminds/squirrel" "github.com/abelith/spellbook/internal/errors" "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 errors.ErrNoDBProvided } if _, err := r.db.ExecContext(ctx, schema); err != nil { return err } return nil } func (r *Repo) Add(ctx context.Context, spell models.Spell) error { tags := strings.Join(spell.Tags, ",") _, err := sq.Insert("spells"). Columns("name", "spell", "short_desc", "description", "tags"). Values(spell.Name, spell.Spell, spell.ShortDesc, spell.Description, tags). PlaceholderFormat(sq.Question). RunWith(r.db). ExecContext(ctx) if err != nil { return err } return nil } func (r *Repo) Delete(ctx context.Context, spellName string) error { _, err := sq.Delete("spells"). Where(sq.Eq{"name": spellName}). PlaceholderFormat(sq.Question). RunWith(r.db). ExecContext(ctx) if err != nil { return err } return nil } func (r *Repo) Get(ctx context.Context, spellName string) (models.Spell, error) { row := sq.Select("id", "name", "spell", "short_desc", "description", "tags"). From("spells"). Where(sq.Eq{"name": spellName}). PlaceholderFormat(sq.Question). RunWith(r.db). QueryRowContext(ctx) return scanSpell(row) } func (r *Repo) GetMany(ctx context.Context, query models.SearchQuery) ([]models.Spell, error) { builder := sq.Select("id", "name", "spell", "short_desc", "description", "tags"). From("spells") if query.Cursor > 0 { builder = builder.Where(sq.Gt{"id": query.Cursor}) } if query.Text != "" { pattern := "%" + query.Text + "%" builder = builder.Where(sq.Or{ sq.Like{"name": pattern}, sq.Like{"spell": pattern}, sq.Like{"description": pattern}, }) } if query.Name != "" { builder = builder.Where(sq.Like{"name": "%" + query.Name + "%"}) } if query.Command != "" { builder = builder.Where(sq.Like{"spell": "%" + query.Command + "%"}) } if query.Description != "" { builder = builder.Where(sq.Like{"description": "%" + query.Description + "%"}) } if len(query.Tags) > 0 { var tagConditions sq.Or for _, tag := range query.Tags { tagConditions = append(tagConditions, sq.Like{"tags": "%" + tag + "%"}) } builder = builder.Where(tagConditions) } if query.Limit > 0 { builder = builder.Limit(uint64(query.Limit)) } builder = builder. OrderBy("id ASC"). PlaceholderFormat(sq.Question) rows, err := builder.RunWith(r.db).QueryContext(ctx) if err != nil { return nil, err } defer rows.Close() var spells []models.Spell 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 }