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
120 lines
3.6 KiB
Go
120 lines
3.6 KiB
Go
package signserver
|
|
|
|
import (
|
|
"strings"
|
|
|
|
dbutil "erupe-ce/common/db"
|
|
"github.com/lib/pq"
|
|
)
|
|
|
|
// SignCharacterRepository implements SignCharacterRepo with PostgreSQL.
|
|
type SignCharacterRepository struct {
|
|
db *dbutil.DB
|
|
}
|
|
|
|
// NewSignCharacterRepository creates a new SignCharacterRepository.
|
|
func NewSignCharacterRepository(db *dbutil.DB) *SignCharacterRepository {
|
|
return &SignCharacterRepository{db: db}
|
|
}
|
|
|
|
func (r *SignCharacterRepository) CountNewCharacters(uid uint32) (int, error) {
|
|
var count int
|
|
err := r.db.QueryRow("SELECT COUNT(*) FROM characters WHERE user_id = $1 AND is_new_character = true", uid).Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
func (r *SignCharacterRepository) CreateCharacter(uid uint32, lastLogin uint32) error {
|
|
_, err := r.db.Exec(`
|
|
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)`,
|
|
uid, lastLogin,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (r *SignCharacterRepository) GetForUser(uid uint32) ([]character, error) {
|
|
characters := make([]character, 0)
|
|
err := r.db.Select(&characters, "SELECT id, is_female, is_new_character, name, unk_desc_string, hr, gr, weapon_type, last_login FROM characters WHERE user_id = $1 AND deleted = false ORDER BY id", uid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return characters, nil
|
|
}
|
|
|
|
func (r *SignCharacterRepository) IsNewCharacter(cid int) (bool, error) {
|
|
var isNew bool
|
|
err := r.db.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", cid).Scan(&isNew)
|
|
return isNew, err
|
|
}
|
|
|
|
func (r *SignCharacterRepository) HardDelete(cid int) error {
|
|
_, err := r.db.Exec("DELETE FROM characters WHERE id = $1", cid)
|
|
return err
|
|
}
|
|
|
|
func (r *SignCharacterRepository) SoftDelete(cid int) error {
|
|
_, err := r.db.Exec("UPDATE characters SET deleted = true WHERE id = $1", cid)
|
|
return err
|
|
}
|
|
|
|
// GetFriends returns friends for a character using parameterized queries
|
|
// (fixes the SQL injection vector from the original string-concatenated approach).
|
|
func (r *SignCharacterRepository) GetFriends(charID uint32) ([]members, error) {
|
|
var friendsCSV string
|
|
err := r.db.QueryRow("SELECT friends FROM characters WHERE id=$1", charID).Scan(&friendsCSV)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if friendsCSV == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
friendsSlice := strings.Split(friendsCSV, ",")
|
|
// Filter out empty strings
|
|
ids := make([]string, 0, len(friendsSlice))
|
|
for _, s := range friendsSlice {
|
|
s = strings.TrimSpace(s)
|
|
if s != "" {
|
|
ids = append(ids, s)
|
|
}
|
|
}
|
|
if len(ids) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Use parameterized ANY($1) instead of string-concatenated WHERE id=X OR id=Y
|
|
friends := make([]members, 0)
|
|
err = r.db.Select(&friends, "SELECT id, name FROM characters WHERE id = ANY($1)", pq.Array(ids))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return friends, nil
|
|
}
|
|
|
|
// GetGuildmates returns guildmates for a character.
|
|
func (r *SignCharacterRepository) GetGuildmates(charID uint32) ([]members, error) {
|
|
var inGuild int
|
|
err := r.db.QueryRow("SELECT count(*) FROM guild_characters WHERE character_id=$1", charID).Scan(&inGuild)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if inGuild == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var guildID int
|
|
err = r.db.QueryRow("SELECT guild_id FROM guild_characters WHERE character_id=$1", charID).Scan(&guildID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
guildmates := make([]members, 0)
|
|
err = r.db.Select(&guildmates, "SELECT character_id AS id, c.name FROM guild_characters gc JOIN characters c ON c.id = gc.character_id WHERE guild_id=$1 AND character_id!=$2", guildID, charID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return guildmates, nil
|
|
}
|