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
153 lines
5.4 KiB
Go
153 lines
5.4 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
dbutil "erupe-ce/common/db"
|
|
)
|
|
|
|
// TowerRepository centralizes all database access for tower-related tables
|
|
// (tower, guilds tower columns, guild_characters tower columns).
|
|
type TowerRepository struct {
|
|
db *dbutil.DB
|
|
}
|
|
|
|
// NewTowerRepository creates a new TowerRepository.
|
|
func NewTowerRepository(db *dbutil.DB) *TowerRepository {
|
|
return &TowerRepository{db: db}
|
|
}
|
|
|
|
// TowerData holds the core tower stats for a character.
|
|
type TowerData struct {
|
|
TR int32
|
|
TRP int32
|
|
TSP int32
|
|
Block1 int32
|
|
Block2 int32
|
|
Skills string
|
|
}
|
|
|
|
// GetTowerData returns tower stats for a character, creating the row if it doesn't exist.
|
|
func (r *TowerRepository) GetTowerData(charID uint32) (TowerData, error) {
|
|
var td TowerData
|
|
err := r.db.QueryRow(
|
|
`SELECT COALESCE(tr, 0), COALESCE(trp, 0), COALESCE(tsp, 0), COALESCE(block1, 0), COALESCE(block2, 0), COALESCE(skills, $1) FROM tower WHERE char_id=$2`,
|
|
EmptyTowerCSV(64), charID,
|
|
).Scan(&td.TR, &td.TRP, &td.TSP, &td.Block1, &td.Block2, &td.Skills)
|
|
if err != nil {
|
|
_, err = r.db.Exec(`INSERT INTO tower (char_id) VALUES ($1)`, charID)
|
|
return TowerData{Skills: EmptyTowerCSV(64)}, err
|
|
}
|
|
return td, nil
|
|
}
|
|
|
|
// GetSkills returns the skills CSV string for a character.
|
|
func (r *TowerRepository) GetSkills(charID uint32) (string, error) {
|
|
var skills string
|
|
err := r.db.QueryRow(`SELECT COALESCE(skills, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(64), charID).Scan(&skills)
|
|
return skills, err
|
|
}
|
|
|
|
// UpdateSkills updates a single skill and deducts TSP cost.
|
|
func (r *TowerRepository) UpdateSkills(charID uint32, skills string, cost int32) error {
|
|
_, err := r.db.Exec(`UPDATE tower SET skills=$1, tsp=tsp-$2 WHERE char_id=$3`, skills, cost, charID)
|
|
return err
|
|
}
|
|
|
|
// UpdateProgress updates tower progress (TR, TRP, TSP, block1).
|
|
func (r *TowerRepository) UpdateProgress(charID uint32, tr, trp, cost, block1 int32) error {
|
|
_, err := r.db.Exec(
|
|
`UPDATE tower SET tr=$1, trp=COALESCE(trp, 0)+$2, tsp=COALESCE(tsp, 0)+$3, block1=COALESCE(block1, 0)+$4 WHERE char_id=$5`,
|
|
tr, trp, cost, block1, charID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// GetGems returns the gems CSV string for a character.
|
|
func (r *TowerRepository) GetGems(charID uint32) (string, error) {
|
|
var gems string
|
|
err := r.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(30), charID).Scan(&gems)
|
|
return gems, err
|
|
}
|
|
|
|
// UpdateGems saves the gems CSV string for a character.
|
|
func (r *TowerRepository) UpdateGems(charID uint32, gems string) error {
|
|
_, err := r.db.Exec(`UPDATE tower SET gems=$1 WHERE char_id=$2`, gems, charID)
|
|
return err
|
|
}
|
|
|
|
// TenrouiraiProgressData holds the guild's tenrouirai (sky corridor) progress.
|
|
type TenrouiraiProgressData struct {
|
|
Page uint8
|
|
Mission1 uint16
|
|
Mission2 uint16
|
|
Mission3 uint16
|
|
}
|
|
|
|
// GetTenrouiraiProgress returns the guild's tower mission page and aggregated mission scores.
|
|
func (r *TowerRepository) GetTenrouiraiProgress(guildID uint32) (TenrouiraiProgressData, error) {
|
|
var p TenrouiraiProgressData
|
|
if err := r.db.QueryRow(`SELECT tower_mission_page FROM guilds WHERE id=$1`, guildID).Scan(&p.Page); err != nil {
|
|
return p, err
|
|
}
|
|
_ = r.db.QueryRow(
|
|
`SELECT SUM(tower_mission_1) AS _, SUM(tower_mission_2) AS _, SUM(tower_mission_3) AS _ FROM guild_characters WHERE guild_id=$1`,
|
|
guildID,
|
|
).Scan(&p.Mission1, &p.Mission2, &p.Mission3)
|
|
return p, nil
|
|
}
|
|
|
|
// GetTenrouiraiMissionScores returns per-character scores for a specific mission index (1-3).
|
|
func (r *TowerRepository) GetTenrouiraiMissionScores(guildID uint32, missionIndex uint8) ([]TenrouiraiCharScore, error) {
|
|
if missionIndex < 1 || missionIndex > 3 {
|
|
missionIndex = (missionIndex % 3) + 1
|
|
}
|
|
rows, err := r.db.Query(
|
|
fmt.Sprintf(
|
|
`SELECT name, tower_mission_%d FROM guild_characters gc INNER JOIN characters c ON gc.character_id = c.id WHERE guild_id=$1 AND tower_mission_%d IS NOT NULL ORDER BY tower_mission_%d DESC`,
|
|
missionIndex, missionIndex, missionIndex,
|
|
),
|
|
guildID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = rows.Close() }()
|
|
var scores []TenrouiraiCharScore
|
|
for rows.Next() {
|
|
var cs TenrouiraiCharScore
|
|
if err := rows.Scan(&cs.Name, &cs.Score); err == nil {
|
|
scores = append(scores, cs)
|
|
}
|
|
}
|
|
return scores, nil
|
|
}
|
|
|
|
// GetGuildTowerRP returns the guild's tower RP.
|
|
func (r *TowerRepository) GetGuildTowerRP(guildID uint32) (uint32, error) {
|
|
var rp uint32
|
|
err := r.db.QueryRow(`SELECT tower_rp FROM guilds WHERE id=$1`, guildID).Scan(&rp)
|
|
return rp, err
|
|
}
|
|
|
|
// GetGuildTowerPageAndRP returns the guild's tower mission page and donated RP.
|
|
func (r *TowerRepository) GetGuildTowerPageAndRP(guildID uint32) (page int, donated int, err error) {
|
|
err = r.db.QueryRow(`SELECT tower_mission_page, tower_rp FROM guilds WHERE id=$1`, guildID).Scan(&page, &donated)
|
|
return
|
|
}
|
|
|
|
// AdvanceTenrouiraiPage increments the guild's tower mission page and resets member mission progress.
|
|
func (r *TowerRepository) AdvanceTenrouiraiPage(guildID uint32) error {
|
|
if _, err := r.db.Exec(`UPDATE guilds SET tower_mission_page=tower_mission_page+1 WHERE id=$1`, guildID); err != nil {
|
|
return err
|
|
}
|
|
_, err := r.db.Exec(`UPDATE guild_characters SET tower_mission_1=NULL, tower_mission_2=NULL, tower_mission_3=NULL WHERE guild_id=$1`, guildID)
|
|
return err
|
|
}
|
|
|
|
// DonateGuildTowerRP adds RP to the guild's tower total.
|
|
func (r *TowerRepository) DonateGuildTowerRP(guildID uint32, rp uint16) error {
|
|
_, err := r.db.Exec(`UPDATE guilds SET tower_rp=tower_rp+$1 WHERE id=$2`, rp, guildID)
|
|
return err
|
|
}
|