mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
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
81 lines
2.6 KiB
Go
81 lines
2.6 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
dbutil "erupe-ce/common/db"
|
|
)
|
|
|
|
// RengokuRepository centralizes all database access for the rengoku_score table.
|
|
type RengokuRepository struct {
|
|
db *dbutil.DB
|
|
}
|
|
|
|
// NewRengokuRepository creates a new RengokuRepository.
|
|
func NewRengokuRepository(db *dbutil.DB) *RengokuRepository {
|
|
return &RengokuRepository{db: db}
|
|
}
|
|
|
|
// UpsertScore ensures a rengoku_score row exists for the character and updates it.
|
|
func (r *RengokuRepository) UpsertScore(charID uint32, maxStagesMp, maxPointsMp, maxStagesSp, maxPointsSp uint32) error {
|
|
var t int
|
|
err := r.db.QueryRow("SELECT character_id FROM rengoku_score WHERE character_id=$1", charID).Scan(&t)
|
|
if err != nil {
|
|
if _, err := r.db.Exec("INSERT INTO rengoku_score (character_id) VALUES ($1)", charID); err != nil {
|
|
return fmt.Errorf("insert rengoku_score: %w", err)
|
|
}
|
|
}
|
|
if _, err := r.db.Exec(
|
|
"UPDATE rengoku_score SET max_stages_mp=$1, max_points_mp=$2, max_stages_sp=$3, max_points_sp=$4 WHERE character_id=$5",
|
|
maxStagesMp, maxPointsMp, maxStagesSp, maxPointsSp, charID,
|
|
); err != nil {
|
|
return fmt.Errorf("update rengoku_score: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// rengokuScoreQuery is the shared FROM/JOIN clause for ranking queries.
|
|
const rengokuScoreQueryRepo = `, c.name FROM rengoku_score rs
|
|
LEFT JOIN characters c ON c.id = rs.character_id
|
|
LEFT JOIN guild_characters gc ON gc.character_id = rs.character_id `
|
|
|
|
// rengokuColumnForLeaderboard maps a leaderboard index to the score column name.
|
|
func rengokuColumnForLeaderboard(leaderboard uint32) string {
|
|
switch leaderboard {
|
|
case 0, 2:
|
|
return "max_stages_mp"
|
|
case 1, 3:
|
|
return "max_points_mp"
|
|
case 4, 6:
|
|
return "max_stages_sp"
|
|
case 5, 7:
|
|
return "max_points_sp"
|
|
default:
|
|
return "max_stages_mp"
|
|
}
|
|
}
|
|
|
|
// rengokuIsGuildFiltered returns true if the leaderboard index is guild-scoped.
|
|
func rengokuIsGuildFiltered(leaderboard uint32) bool {
|
|
return leaderboard == 2 || leaderboard == 3 || leaderboard == 6 || leaderboard == 7
|
|
}
|
|
|
|
// GetRanking returns rengoku scores for the given leaderboard.
|
|
// For guild-scoped leaderboards (2,3,6,7), guildID filters the results.
|
|
func (r *RengokuRepository) GetRanking(leaderboard uint32, guildID uint32) ([]RengokuScore, error) {
|
|
col := rengokuColumnForLeaderboard(leaderboard)
|
|
var result []RengokuScore
|
|
var err error
|
|
if rengokuIsGuildFiltered(leaderboard) {
|
|
err = r.db.Select(&result,
|
|
fmt.Sprintf("SELECT %s AS score %s WHERE guild_id=$1 ORDER BY %s DESC", col, rengokuScoreQueryRepo, col),
|
|
guildID,
|
|
)
|
|
} else {
|
|
err = r.db.Select(&result,
|
|
fmt.Sprintf("SELECT %s AS score %s ORDER BY %s DESC", col, rengokuScoreQueryRepo, col),
|
|
)
|
|
}
|
|
return result, err
|
|
}
|