Files

162 lines
3.7 KiB
Go

package queries
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/alexedwards/argon2id"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
)
type AdminQueries struct {
db *sqlx.DB
}
func NewAdminQueries(db *sqlx.DB) *AdminQueries {
return &AdminQueries{db: db}
}
type Admin struct {
ID uuid.UUID `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"-"`
CreatedAt time.Time `json:"created_at"`
}
// CreateAdminIfNotExists creates an admin user if they don't already exist
func (q *AdminQueries) CreateAdminIfNotExists(username, email, password string) error {
ctx := context.Background()
// Check if admin already exists
var exists bool
err := q.db.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM users WHERE username = $1)", username).Scan(&exists)
if err != nil {
return fmt.Errorf("failed to check if admin exists: %w", err)
}
if exists {
return nil // Admin already exists, nothing to do
}
// Hash the password
hashedPassword, err := argon2id.CreateHash(password, argon2id.DefaultParams)
if err != nil {
return fmt.Errorf("failed to hash password: %w", err)
}
// Create the admin
query := `
INSERT INTO users (username, email, password_hash, created_at)
VALUES ($1, $2, $3, NOW())
`
_, err = q.db.ExecContext(ctx, query, username, email, hashedPassword)
if err != nil {
return fmt.Errorf("failed to create admin: %w", err)
}
return nil
}
// UpdateAdminPassword updates the admin's password (always updates from .env)
func (q *AdminQueries) UpdateAdminPassword(username, password string) error {
ctx := context.Background()
// Hash the password
hashedPassword, err := argon2id.CreateHash(password, argon2id.DefaultParams)
if err != nil {
return fmt.Errorf("failed to hash password: %w", err)
}
// Update the password
query := `
UPDATE users
SET password_hash = $1
WHERE username = $2
`
result, err := q.db.ExecContext(ctx, query, hashedPassword, username)
if err != nil {
return fmt.Errorf("failed to update admin password: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get rows affected: %w", err)
}
if rowsAffected == 0 {
return fmt.Errorf("admin not found")
}
return nil
}
// VerifyAdminCredentials validates username and password against the database hash
func (q *AdminQueries) VerifyAdminCredentials(username, password string) (*Admin, error) {
ctx := context.Background()
var admin Admin
query := `
SELECT id, username, email, password_hash, created_at
FROM users
WHERE username = $1
`
err := q.db.QueryRowContext(ctx, query, username).Scan(
&admin.ID,
&admin.Username,
&admin.Email,
&admin.Password,
&admin.CreatedAt,
)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("admin not found")
}
if err != nil {
return nil, fmt.Errorf("failed to query admin: %w", err)
}
// Verify the password
match, err := argon2id.ComparePasswordAndHash(password, admin.Password)
if err != nil {
return nil, fmt.Errorf("failed to compare password: %w", err)
}
if !match {
return nil, fmt.Errorf("invalid credentials")
}
return &admin, nil
}
// GetAdminByUsername retrieves admin by username (for JWT claims)
func (q *AdminQueries) GetAdminByUsername(username string) (*Admin, error) {
ctx := context.Background()
var admin Admin
query := `
SELECT id, username, email, created_at
FROM users
WHERE username = $1
`
err := q.db.QueryRowContext(ctx, query, username).Scan(
&admin.ID,
&admin.Username,
&admin.Email,
&admin.CreatedAt,
)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("admin not found")
}
if err != nil {
return nil, fmt.Errorf("failed to query admin: %w", err)
}
return &admin, nil
}