mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
Move scan loops from handlers into repository methods so that interfaces return typed slices instead of leaking database cursors. This fixes resource leaks (7 of 12 call sites never closed rows) and makes all 12 methods mockable for unit tests. Affected repos: CafeRepo, ShopRepo, EventRepo, RengokuRepo, DivaRepo, ScenarioRepo, MiscRepo, MercenaryRepo. New structs: DivaEvent, MercenaryLoan, GuildHuntCatUsage. EventRepo.GetEventQuests left as-is (requires broader Server refactor).
81 lines
2.6 KiB
Go
81 lines
2.6 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
)
|
|
|
|
// RengokuRepository centralizes all database access for the rengoku_score table.
|
|
type RengokuRepository struct {
|
|
db *sqlx.DB
|
|
}
|
|
|
|
// NewRengokuRepository creates a new RengokuRepository.
|
|
func NewRengokuRepository(db *sqlx.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
|
|
}
|