Files
spellbook/internal/adapters/sqlite/sqlite.go
T
abelith 80a90b65bb chore: sql builder instead of row sql
feat: extensive search queries
2026-08-15 13:51:15 +05:00

165 lines
3.6 KiB
Go

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
}