Phase 1: database schema, migrations, repository, user seeding
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user