package setup import ( "context" "database/sql" "errors" ) var ErrSetupAlreadyDone = errors.New("setup already completed") // Storer is the interface for setup-related DB operations. type Storer interface { NeedsSetup(ctx context.Context) (bool, error) CreateAdmin(ctx context.Context, name, email, hashedPassword string) (int64, error) } type Store struct { db *sql.DB } func NewStore(db *sql.DB) *Store { return &Store{db: db} } // NeedsSetup returns true when the volunteers table has zero rows. func (s *Store) NeedsSetup(ctx context.Context) (bool, error) { var count int err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM volunteers`).Scan(&count) if err != nil { return false, err } return count == 0, nil } // CreateAdmin atomically checks that no users exist and inserts the first admin. func (s *Store) CreateAdmin(ctx context.Context, name, email, hashedPassword string) (int64, error) { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return 0, err } defer tx.Rollback() var count int if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM volunteers`).Scan(&count); err != nil { return 0, err } if count > 0 { return 0, ErrSetupAlreadyDone } res, err := tx.ExecContext(ctx, `INSERT INTO volunteers (name, email, password, role, active, operational_roles) VALUES (?, ?, ?, 'admin', 1, '')`, name, email, hashedPassword, ) if err != nil { return 0, err } id, err := res.LastInsertId() if err != nil { return 0, err } if err := tx.Commit(); err != nil { return 0, err } return id, nil }