Files
Erupe/server/api/repo_character.go
Houmgaor ecfe58ffb4 feat: add SQLite support, setup wizard enhancements, and live dashboard
Add zero-dependency SQLite mode so users can run Erupe without
PostgreSQL. A transparent db.DB wrapper auto-translates PostgreSQL
SQL ($N placeholders, now(), ::casts, ILIKE, public. prefix,
TRUNCATE) for SQLite at runtime — all 28 repo files use the wrapper
with no per-query changes needed.

Setup wizard gains two new steps: quest file detection with download
link, and gameplay presets (solo/small/community/rebalanced). The API
server gets a /dashboard endpoint with auto-refreshing stats.

CI release workflow now builds and pushes Docker images to GHCR
alongside binary artifacts on tag push.

Key changes:
- common/db: DB/Tx wrapper with 6 SQL translation rules
- server/migrations/sqlite: full SQLite schema (0001-0005)
- config: Database.Driver field ("postgres" or "sqlite")
- main.go: SQLite connection with WAL mode, single writer
- server/setup: quest check + preset selection steps
- server/api: /dashboard with live stats
- .github/workflows: Docker in release, deduplicate docker.yml
2026-03-05 18:00:30 +01:00

88 lines
2.7 KiB
Go

package api
import (
"context"
dbutil "erupe-ce/common/db"
)
// APICharacterRepository implements APICharacterRepo with PostgreSQL.
type APICharacterRepository struct {
db *dbutil.DB
}
// NewAPICharacterRepository creates a new APICharacterRepository.
func NewAPICharacterRepository(db *dbutil.DB) *APICharacterRepository {
return &APICharacterRepository{db: db}
}
func (r *APICharacterRepository) GetNewCharacter(ctx context.Context, userID uint32) (Character, error) {
var character Character
err := r.db.GetContext(ctx, &character,
"SELECT id, name, is_female, weapon_type, hr, gr, last_login FROM characters WHERE is_new_character = true AND user_id = $1 LIMIT 1",
userID,
)
return character, err
}
func (r *APICharacterRepository) CountForUser(ctx context.Context, userID uint32) (int, error) {
var count int
err := r.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM characters WHERE user_id = $1", userID).Scan(&count)
return count, err
}
func (r *APICharacterRepository) Create(ctx context.Context, userID uint32, lastLogin uint32) (Character, error) {
var character Character
err := r.db.GetContext(ctx, &character, `
INSERT INTO characters (
user_id, is_female, is_new_character, name, unk_desc_string,
hr, gr, weapon_type, last_login
)
VALUES ($1, false, true, '', '', 0, 0, 0, $2)
RETURNING id, name, is_female, weapon_type, hr, gr, last_login`,
userID, lastLogin,
)
return character, err
}
func (r *APICharacterRepository) IsNew(charID uint32) (bool, error) {
var isNew bool
err := r.db.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", charID).Scan(&isNew)
return isNew, err
}
func (r *APICharacterRepository) HardDelete(charID uint32) error {
_, err := r.db.Exec("DELETE FROM characters WHERE id = $1", charID)
return err
}
func (r *APICharacterRepository) SoftDelete(charID uint32) error {
_, err := r.db.Exec("UPDATE characters SET deleted = true WHERE id = $1", charID)
return err
}
func (r *APICharacterRepository) GetForUser(ctx context.Context, userID uint32) ([]Character, error) {
var characters []Character
err := r.db.SelectContext(
ctx, &characters, `
SELECT id, name, is_female, weapon_type, hr, gr, last_login
FROM characters
WHERE user_id = $1 AND deleted = false AND is_new_character = false ORDER BY id ASC`,
userID,
)
if err != nil {
return nil, err
}
return characters, nil
}
func (r *APICharacterRepository) ExportSave(ctx context.Context, userID, charID uint32) (map[string]interface{}, error) {
row := r.db.QueryRowxContext(ctx, "SELECT * FROM characters WHERE id=$1 AND user_id=$2", charID, userID)
result := make(map[string]interface{})
err := row.MapScan(result)
if err != nil {
return nil, err
}
return result, nil
}