Phase 1: database schema, migrations, repository, user seeding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jānis Kacēns
2026-05-11 11:38:31 +03:00
parent 34eb47b595
commit 0bc9160d97
8 changed files with 668 additions and 1 deletions
+57
View File
@@ -0,0 +1,57 @@
package db
import (
"database/sql"
_ "embed"
"fmt"
_ "modernc.org/sqlite"
"golang.org/x/crypto/bcrypt"
"qbank/internal/config"
)
//go:embed schema.sql
var schema string
func Open(path string) (*sql.DB, error) {
db, err := sql.Open("sqlite", path)
if err != nil {
return nil, fmt.Errorf("open sqlite: %w", err)
}
db.SetMaxOpenConns(1)
for _, stmt := range []string{
"PRAGMA journal_mode=WAL",
"PRAGMA foreign_keys=ON",
} {
if _, err := db.Exec(stmt); err != nil {
db.Close()
return nil, fmt.Errorf("pragma %q: %w", stmt, err)
}
}
if _, err := db.Exec(schema); err != nil {
db.Close()
return nil, fmt.Errorf("apply schema: %w", err)
}
return db, nil
}
func Seed(db *sql.DB, users []config.AdminUser) error {
var count int
if err := db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count); err != nil {
return fmt.Errorf("count users: %w", err)
}
if count > 0 {
return nil
}
for _, u := range users {
hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("hash password for %q: %w", u.Name, err)
}
if _, err := db.Exec("INSERT INTO users (name, password_hash) VALUES (?, ?)", u.Name, string(hash)); err != nil {
return fmt.Errorf("insert user %q: %w", u.Name, err)
}
}
return nil
}