From 3673d318a132bfc95dfbe2d6f45d6e99b296d05d Mon Sep 17 00:00:00 2001 From: Kirillkriv Date: Mon, 3 Aug 2026 22:15:26 +0500 Subject: [PATCH] feat - sqlite adapter --- internal/adapters/sqlite/sqlite.go | 136 +++++++++++++++++++++++++++++ internal/models/models.go | 9 +- 2 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 internal/adapters/sqlite/sqlite.go diff --git a/internal/adapters/sqlite/sqlite.go b/internal/adapters/sqlite/sqlite.go new file mode 100644 index 0000000..46103dd --- /dev/null +++ b/internal/adapters/sqlite/sqlite.go @@ -0,0 +1,136 @@ +package sqlite + +import ( + "context" + "database/sql" + "fmt" + "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 +} diff --git a/internal/models/models.go b/internal/models/models.go index 90560e3..ccd7b52 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -6,9 +6,12 @@ import ( ) type Spell struct { - Name string - Spell string - Tags []string + Id int + Name string + Spell string + ShortDesc string + Description string + Tags []string } type SpellCast struct {