mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-04-05 15:32:29 +02:00
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
This commit is contained in:
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -4,8 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
|
|||||||
58
.github/workflows/release.yml
vendored
58
.github/workflows/release.yml
vendored
@@ -7,6 +7,13 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
packages: write
|
||||||
|
attestations: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -56,9 +63,49 @@ jobs:
|
|||||||
path: erupe-${{ matrix.os_name }}.zip
|
path: erupe-${{ matrix.os_name }}.zip
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
|
docker:
|
||||||
|
name: Build and Push Docker Image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
|
- name: Generate artifact attestation
|
||||||
|
uses: actions/attest-build-provenance@v2
|
||||||
|
with:
|
||||||
|
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
subject-digest: ${{ steps.push.outputs.digest }}
|
||||||
|
push-to-registry: true
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Create Release
|
name: Create Release
|
||||||
needs: build
|
needs: [build, docker]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -76,6 +123,15 @@ jobs:
|
|||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
body: |
|
||||||
|
## Docker Image
|
||||||
|
|
||||||
|
This release is also available as a Docker image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||||
|
```
|
||||||
|
append_body: true
|
||||||
files: |
|
files: |
|
||||||
artifacts/Linux-amd64/erupe-Linux-amd64.zip
|
artifacts/Linux-amd64/erupe-Linux-amd64.zip
|
||||||
artifacts/Windows-amd64/erupe-Windows-amd64.zip
|
artifacts/Windows-amd64/erupe-Windows-amd64.zip
|
||||||
|
|||||||
68
common/db/adapter.go
Normal file
68
common/db/adapter.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// Package db provides a database adapter that transparently translates
|
||||||
|
// PostgreSQL-style SQL to the active driver's dialect.
|
||||||
|
//
|
||||||
|
// When the driver is "sqlite", queries are rewritten on the fly:
|
||||||
|
// - $1, $2, ... → ?, ?, ...
|
||||||
|
// - now() → CURRENT_TIMESTAMP
|
||||||
|
// - ::type casts → removed
|
||||||
|
// - ILIKE → LIKE (SQLite LIKE is case-insensitive for ASCII)
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsSQLite reports whether the given sqlx.DB is backed by a SQLite driver.
|
||||||
|
func IsSQLite(db *sqlx.DB) bool {
|
||||||
|
return db.DriverName() == "sqlite" || db.DriverName() == "sqlite3"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapt rewrites a PostgreSQL query for the active driver.
|
||||||
|
// For Postgres it's a no-op. For SQLite it translates placeholders and
|
||||||
|
// Postgres-specific syntax.
|
||||||
|
func Adapt(db *sqlx.DB, query string) string {
|
||||||
|
if !IsSQLite(db) {
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
return AdaptSQL(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// castRe matches Postgres type casts like ::int, ::text, ::timestamptz,
|
||||||
|
// ::character varying, etc.
|
||||||
|
// castRe matches Postgres type casts: ::int, ::text, ::timestamptz,
|
||||||
|
// ::character varying(N), etc. The space is allowed only when followed
|
||||||
|
// by a word char (e.g. "character varying") to avoid eating trailing spaces.
|
||||||
|
var castRe = regexp.MustCompile(`::[a-zA-Z_]\w*(?:\s+\w+)*(?:\([^)]*\))?`)
|
||||||
|
|
||||||
|
// dollarParamRe matches Postgres-style positional parameters: $1, $2, etc.
|
||||||
|
var dollarParamRe = regexp.MustCompile(`\$\d+`)
|
||||||
|
|
||||||
|
// AdaptSQL translates a PostgreSQL query to SQLite-compatible SQL.
|
||||||
|
// Exported so it can be tested without a real DB connection.
|
||||||
|
func AdaptSQL(query string) string {
|
||||||
|
// 1. Replace now() with CURRENT_TIMESTAMP
|
||||||
|
query = strings.ReplaceAll(query, "now()", "CURRENT_TIMESTAMP")
|
||||||
|
query = strings.ReplaceAll(query, "NOW()", "CURRENT_TIMESTAMP")
|
||||||
|
|
||||||
|
// 2. Strip Postgres type casts (::int, ::text, ::timestamptz, etc.)
|
||||||
|
query = castRe.ReplaceAllString(query, "")
|
||||||
|
|
||||||
|
// 3. ILIKE → LIKE (SQLite LIKE is case-insensitive for ASCII by default)
|
||||||
|
query = strings.ReplaceAll(query, " ILIKE ", " LIKE ")
|
||||||
|
query = strings.ReplaceAll(query, " ilike ", " LIKE ")
|
||||||
|
|
||||||
|
// 4. Strip "public." schema prefix (SQLite has no schemas)
|
||||||
|
query = strings.ReplaceAll(query, "public.", "")
|
||||||
|
|
||||||
|
// 5. TRUNCATE → DELETE FROM (SQLite has no TRUNCATE)
|
||||||
|
query = strings.ReplaceAll(query, "TRUNCATE ", "DELETE FROM ")
|
||||||
|
query = strings.ReplaceAll(query, "truncate ", "DELETE FROM ")
|
||||||
|
|
||||||
|
// 6. Replace $1,$2,... → ?,?,...
|
||||||
|
query = dollarParamRe.ReplaceAllString(query, "?")
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
||||||
83
common/db/adapter_test.go
Normal file
83
common/db/adapter_test.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdaptSQL(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "placeholder rebind",
|
||||||
|
input: "SELECT * FROM users WHERE id=$1 AND name=$2",
|
||||||
|
want: "SELECT * FROM users WHERE id=? AND name=?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "now() replacement",
|
||||||
|
input: "UPDATE characters SET guild_post_checked=now() WHERE id=$1",
|
||||||
|
want: "UPDATE characters SET guild_post_checked=CURRENT_TIMESTAMP WHERE id=?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "type cast removal",
|
||||||
|
input: "UPDATE users SET frontier_points=frontier_points::int - $1 WHERE id=$2 RETURNING frontier_points",
|
||||||
|
want: "UPDATE users SET frontier_points=frontier_points - ? WHERE id=? RETURNING frontier_points",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "text cast removal",
|
||||||
|
input: "SELECT COALESCE(friends, ''::text) FROM characters WHERE id=$1",
|
||||||
|
want: "SELECT COALESCE(friends, '') FROM characters WHERE id=?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timestamptz cast removal",
|
||||||
|
input: "SELECT COALESCE(created_at, '2000-01-01'::timestamptz) FROM guilds WHERE id=$1",
|
||||||
|
want: "SELECT COALESCE(created_at, '2000-01-01') FROM guilds WHERE id=?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ILIKE to LIKE",
|
||||||
|
input: "SELECT * FROM characters WHERE name ILIKE $1",
|
||||||
|
want: "SELECT * FROM characters WHERE name LIKE ?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "character varying cast",
|
||||||
|
input: "DEFAULT ''::character varying",
|
||||||
|
want: "DEFAULT ''",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no changes for standard SQL",
|
||||||
|
input: "SELECT COUNT(*) FROM users",
|
||||||
|
want: "SELECT COUNT(*) FROM users",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NOW uppercase",
|
||||||
|
input: "INSERT INTO events (start_time) VALUES (NOW())",
|
||||||
|
want: "INSERT INTO events (start_time) VALUES (CURRENT_TIMESTAMP)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multi-digit params",
|
||||||
|
input: "INSERT INTO t (a,b,c) VALUES ($1,$2,$10)",
|
||||||
|
want: "INSERT INTO t (a,b,c) VALUES (?,?,?)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "public schema prefix",
|
||||||
|
input: "INSERT INTO public.distributions_accepted VALUES ($1, $2)",
|
||||||
|
want: "INSERT INTO distributions_accepted VALUES (?, ?)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TRUNCATE to DELETE FROM",
|
||||||
|
input: "TRUNCATE public.cafebonus;",
|
||||||
|
want: "DELETE FROM cafebonus;",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
got := AdaptSQL(tc.input)
|
||||||
|
if got != tc.want {
|
||||||
|
t.Errorf("\ngot: %s\nwant: %s", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
149
common/db/db.go
Normal file
149
common/db/db.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
// Package db provides a transparent database wrapper that rewrites
|
||||||
|
// PostgreSQL-style SQL for SQLite when needed.
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DB wraps *sqlx.DB and transparently adapts PostgreSQL queries for SQLite.
|
||||||
|
// For PostgreSQL, all methods are simple pass-throughs with zero overhead.
|
||||||
|
type DB struct {
|
||||||
|
*sqlx.DB
|
||||||
|
sqlite bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap creates a DB wrapper around an existing *sqlx.DB connection.
|
||||||
|
// Returns nil if db is nil.
|
||||||
|
func Wrap(db *sqlx.DB) *DB {
|
||||||
|
if db == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &DB{
|
||||||
|
DB: db,
|
||||||
|
sqlite: IsSQLite(db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) adapt(query string) string {
|
||||||
|
if !d.sqlite {
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
return AdaptSQL(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes a query without returning any rows.
|
||||||
|
func (d *DB) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
return d.DB.Exec(d.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecContext executes a query without returning any rows.
|
||||||
|
func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
return d.DB.ExecContext(ctx, d.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query executes a query that returns rows.
|
||||||
|
func (d *DB) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
return d.DB.Query(d.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryContext executes a query that returns rows.
|
||||||
|
func (d *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
return d.DB.QueryContext(ctx, d.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRow executes a query that is expected to return at most one row.
|
||||||
|
func (d *DB) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||||
|
return d.DB.QueryRow(d.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRowContext executes a query that is expected to return at most one row.
|
||||||
|
func (d *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
||||||
|
return d.DB.QueryRowContext(ctx, d.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get queries a single row and scans it into dest.
|
||||||
|
func (d *DB) Get(dest interface{}, query string, args ...interface{}) error {
|
||||||
|
return d.DB.Get(dest, d.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select queries multiple rows and scans them into dest.
|
||||||
|
func (d *DB) Select(dest interface{}, query string, args ...interface{}) error {
|
||||||
|
return d.DB.Select(dest, d.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queryx executes a query that returns sqlx.Rows.
|
||||||
|
func (d *DB) Queryx(query string, args ...interface{}) (*sqlx.Rows, error) {
|
||||||
|
return d.DB.Queryx(d.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRowx executes a query that returns a sqlx.Row.
|
||||||
|
func (d *DB) QueryRowx(query string, args ...interface{}) *sqlx.Row {
|
||||||
|
return d.DB.QueryRowx(d.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRowxContext executes a query that returns a sqlx.Row.
|
||||||
|
func (d *DB) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *sqlx.Row {
|
||||||
|
return d.DB.QueryRowxContext(ctx, d.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeginTxx starts a new transaction with context and options.
|
||||||
|
// The returned Tx wrapper adapts queries the same way as DB.
|
||||||
|
func (d *DB) BeginTxx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
|
||||||
|
tx, err := d.DB.BeginTxx(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Tx{Tx: tx, sqlite: d.sqlite}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tx wraps *sqlx.Tx and transparently adapts PostgreSQL queries for SQLite.
|
||||||
|
type Tx struct {
|
||||||
|
*sqlx.Tx
|
||||||
|
sqlite bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tx) adapt(query string) string {
|
||||||
|
if !t.sqlite {
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
return AdaptSQL(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes a query without returning any rows.
|
||||||
|
func (t *Tx) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
return t.Tx.Exec(t.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecContext executes a query without returning any rows.
|
||||||
|
func (t *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
return t.Tx.ExecContext(ctx, t.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query executes a query that returns rows.
|
||||||
|
func (t *Tx) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
return t.Tx.Query(t.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRow executes a query that is expected to return at most one row.
|
||||||
|
func (t *Tx) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||||
|
return t.Tx.QueryRow(t.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queryx executes a query that returns sqlx.Rows.
|
||||||
|
func (t *Tx) Queryx(query string, args ...interface{}) (*sqlx.Rows, error) {
|
||||||
|
return t.Tx.Queryx(t.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRowx executes a query that returns a sqlx.Row.
|
||||||
|
func (t *Tx) QueryRowx(query string, args ...interface{}) *sqlx.Row {
|
||||||
|
return t.Tx.QueryRowx(t.adapt(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get queries a single row and scans it into dest.
|
||||||
|
func (t *Tx) Get(dest interface{}, query string, args ...interface{}) error {
|
||||||
|
return t.Tx.Get(dest, t.adapt(query), args...)
|
||||||
|
}
|
||||||
@@ -231,8 +231,12 @@ type Course struct {
|
|||||||
Enabled bool
|
Enabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database holds the postgres database config.
|
// Database holds the database config.
|
||||||
|
// Driver can be "postgres" (default) or "sqlite".
|
||||||
|
// When Driver is "sqlite", only Database (file path) is used;
|
||||||
|
// Host, Port, User, and Password are ignored.
|
||||||
type Database struct {
|
type Database struct {
|
||||||
|
Driver string // "postgres" or "sqlite"
|
||||||
Host string
|
Host string
|
||||||
Port int
|
Port int
|
||||||
User string
|
User string
|
||||||
@@ -454,7 +458,8 @@ func registerDefaults() {
|
|||||||
{Name: "EXRenewing", Enabled: true},
|
{Name: "EXRenewing", Enabled: true},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Database (Password deliberately has no default)
|
// Database (Password deliberately has no default for postgres)
|
||||||
|
viper.SetDefault("Database.Driver", "postgres")
|
||||||
viper.SetDefault("Database.Host", "localhost")
|
viper.SetDefault("Database.Host", "localhost")
|
||||||
viper.SetDefault("Database.Port", 5432)
|
viper.SetDefault("Database.Port", 5432)
|
||||||
viper.SetDefault("Database.User", "postgres")
|
viper.SetDefault("Database.User", "postgres")
|
||||||
|
|||||||
11
go.mod
11
go.mod
@@ -16,13 +16,18 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect
|
github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.1 // indirect
|
github.com/gorilla/websocket v1.5.1 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
@@ -31,9 +36,13 @@ require (
|
|||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.49.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
modernc.org/libc v1.67.6 // indirect
|
||||||
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
modernc.org/memory v1.11.0 // indirect
|
||||||
|
modernc.org/sqlite v1.46.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
22
go.sum
22
go.sum
@@ -54,6 +54,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
@@ -109,6 +111,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
@@ -124,6 +127,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
|
|||||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||||
@@ -159,10 +164,14 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
|||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
|
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
|
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
@@ -171,6 +180,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
@@ -237,6 +248,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
|||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
|
||||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||||
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||||
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@@ -348,6 +361,7 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
@@ -521,6 +535,14 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
||||||
|
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
||||||
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
||||||
|
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
|
|||||||
54
main.go
54
main.go
@@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Temporary DB auto clean on startup for quick development & testing.
|
// Temporary DB auto clean on startup for quick development & testing.
|
||||||
@@ -108,7 +109,7 @@ func main() {
|
|||||||
logger.Info(fmt.Sprintf("Starting Erupe (9.3b-%s)", Commit()))
|
logger.Info(fmt.Sprintf("Starting Erupe (9.3b-%s)", Commit()))
|
||||||
logger.Info(fmt.Sprintf("Client Mode: %s (%d)", config.ClientMode, config.RealClientMode))
|
logger.Info(fmt.Sprintf("Client Mode: %s (%d)", config.ClientMode, config.RealClientMode))
|
||||||
|
|
||||||
if config.Database.Password == "" {
|
if config.Database.Driver != "sqlite" && config.Database.Password == "" {
|
||||||
preventClose(config, "Database password is blank")
|
preventClose(config, "Database password is blank")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,19 +137,38 @@ func main() {
|
|||||||
logger.Info("Discord: Disabled")
|
logger.Info("Discord: Disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the postgres DB pool.
|
// Create the DB pool.
|
||||||
connectString := fmt.Sprintf(
|
var db *sqlx.DB
|
||||||
"host='%s' port='%d' user='%s' password='%s' dbname='%s' sslmode=disable",
|
if config.Database.Driver == "sqlite" {
|
||||||
config.Database.Host,
|
dbPath := config.Database.Database
|
||||||
config.Database.Port,
|
if dbPath == "" || dbPath == "erupe" {
|
||||||
config.Database.User,
|
dbPath = "erupe.db"
|
||||||
config.Database.Password,
|
}
|
||||||
config.Database.Database,
|
db, err = sqlx.Open("sqlite", dbPath+"?_pragma=journal_mode(WAL)&_pragma=foreign_keys(1)")
|
||||||
)
|
if err != nil {
|
||||||
|
preventClose(config, fmt.Sprintf("Database: Failed to open SQLite %s, %s", dbPath, err.Error()))
|
||||||
db, err := sqlx.Open("postgres", connectString)
|
}
|
||||||
if err != nil {
|
// SQLite only supports one writer at a time.
|
||||||
preventClose(config, fmt.Sprintf("Database: Failed to open, %s", err.Error()))
|
db.SetMaxOpenConns(1)
|
||||||
|
logger.Info(fmt.Sprintf("Database: SQLite opened (%s)", dbPath))
|
||||||
|
} else {
|
||||||
|
connectString := fmt.Sprintf(
|
||||||
|
"host='%s' port='%d' user='%s' password='%s' dbname='%s' sslmode=disable",
|
||||||
|
config.Database.Host,
|
||||||
|
config.Database.Port,
|
||||||
|
config.Database.User,
|
||||||
|
config.Database.Password,
|
||||||
|
config.Database.Database,
|
||||||
|
)
|
||||||
|
db, err = sqlx.Open("postgres", connectString)
|
||||||
|
if err != nil {
|
||||||
|
preventClose(config, fmt.Sprintf("Database: Failed to open, %s", err.Error()))
|
||||||
|
}
|
||||||
|
// Configure connection pool to avoid exhausting PostgreSQL under load.
|
||||||
|
db.SetMaxOpenConns(50)
|
||||||
|
db.SetMaxIdleConns(10)
|
||||||
|
db.SetConnMaxLifetime(5 * time.Minute)
|
||||||
|
db.SetConnMaxIdleTime(2 * time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the DB connection.
|
// Test the DB connection.
|
||||||
@@ -157,12 +177,6 @@ func main() {
|
|||||||
preventClose(config, fmt.Sprintf("Database: Failed to ping, %s", err.Error()))
|
preventClose(config, fmt.Sprintf("Database: Failed to ping, %s", err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure connection pool to avoid exhausting PostgreSQL under load.
|
|
||||||
db.SetMaxOpenConns(50)
|
|
||||||
db.SetMaxIdleConns(10)
|
|
||||||
db.SetConnMaxLifetime(5 * time.Minute)
|
|
||||||
db.SetConnMaxIdleTime(2 * time.Minute)
|
|
||||||
|
|
||||||
logger.Info("Database: Started successfully")
|
logger.Info("Database: Started successfully")
|
||||||
|
|
||||||
// Run database migrations
|
// Run database migrations
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
|
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -33,6 +35,7 @@ type APIServer struct {
|
|||||||
sessionRepo APISessionRepo
|
sessionRepo APISessionRepo
|
||||||
eventRepo APIEventRepo
|
eventRepo APIEventRepo
|
||||||
httpServer *http.Server
|
httpServer *http.Server
|
||||||
|
startTime time.Time
|
||||||
isShuttingDown bool
|
isShuttingDown bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,19 +48,26 @@ func NewAPIServer(config *Config) *APIServer {
|
|||||||
httpServer: &http.Server{},
|
httpServer: &http.Server{},
|
||||||
}
|
}
|
||||||
if config.DB != nil {
|
if config.DB != nil {
|
||||||
s.userRepo = NewAPIUserRepository(config.DB)
|
wdb := dbutil.Wrap(config.DB)
|
||||||
s.charRepo = NewAPICharacterRepository(config.DB)
|
s.userRepo = NewAPIUserRepository(wdb)
|
||||||
s.sessionRepo = NewAPISessionRepository(config.DB)
|
s.charRepo = NewAPICharacterRepository(wdb)
|
||||||
s.eventRepo = NewAPIEventRepository(config.DB)
|
s.sessionRepo = NewAPISessionRepository(wdb)
|
||||||
|
s.eventRepo = NewAPIEventRepository(wdb)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the server in a new goroutine.
|
// Start starts the server in a new goroutine.
|
||||||
func (s *APIServer) Start() error {
|
func (s *APIServer) Start() error {
|
||||||
|
s.startTime = time.Now()
|
||||||
|
|
||||||
// Set up the routes responsible for serving the launcher HTML, serverlist, unique name check, and JP auth.
|
// Set up the routes responsible for serving the launcher HTML, serverlist, unique name check, and JP auth.
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
// Dashboard routes (before catch-all)
|
||||||
|
r.HandleFunc("/dashboard", s.Dashboard)
|
||||||
|
r.HandleFunc("/api/dashboard/stats", s.DashboardStatsJSON).Methods("GET")
|
||||||
|
|
||||||
// Legacy routes (unchanged, no method enforcement)
|
// Legacy routes (unchanged, no method enforcement)
|
||||||
r.HandleFunc("/launcher", s.Launcher)
|
r.HandleFunc("/launcher", s.Launcher)
|
||||||
r.HandleFunc("/login", s.Login)
|
r.HandleFunc("/login", s.Login)
|
||||||
|
|||||||
137
server/api/dashboard.go
Normal file
137
server/api/dashboard.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed dashboard.html
|
||||||
|
var dashboardHTML string
|
||||||
|
|
||||||
|
var dashboardTmpl = template.Must(template.New("dashboard").Parse(dashboardHTML))
|
||||||
|
|
||||||
|
// DashboardStats is the JSON payload returned by GET /api/dashboard/stats.
|
||||||
|
type DashboardStats struct {
|
||||||
|
Uptime string `json:"uptime"`
|
||||||
|
ServerVersion string `json:"serverVersion"`
|
||||||
|
ClientMode string `json:"clientMode"`
|
||||||
|
OnlinePlayers int `json:"onlinePlayers"`
|
||||||
|
TotalAccounts int `json:"totalAccounts"`
|
||||||
|
TotalCharacters int `json:"totalCharacters"`
|
||||||
|
Channels []ChannelInfo `json:"channels"`
|
||||||
|
DatabaseOK bool `json:"databaseOK"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelInfo describes a single channel server entry from the servers table.
|
||||||
|
type ChannelInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
Players int `json:"players"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dashboard serves the embedded HTML dashboard page at /dashboard.
|
||||||
|
func (s *APIServer) Dashboard(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
if err := dashboardTmpl.Execute(w, nil); err != nil {
|
||||||
|
s.logger.Error("Failed to render dashboard", zap.Error(err))
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DashboardStatsJSON serves GET /api/dashboard/stats with live server statistics.
|
||||||
|
func (s *APIServer) DashboardStatsJSON(w http.ResponseWriter, r *http.Request) {
|
||||||
|
stats := DashboardStats{
|
||||||
|
ServerVersion: "Erupe-CE",
|
||||||
|
ClientMode: s.erupeConfig.ClientMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute uptime.
|
||||||
|
if !s.startTime.IsZero() {
|
||||||
|
stats.Uptime = formatDuration(time.Since(s.startTime))
|
||||||
|
} else {
|
||||||
|
stats.Uptime = "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check database connectivity.
|
||||||
|
if s.db != nil {
|
||||||
|
if err := s.db.Ping(); err != nil {
|
||||||
|
s.logger.Warn("Dashboard: database ping failed", zap.Error(err))
|
||||||
|
stats.DatabaseOK = false
|
||||||
|
} else {
|
||||||
|
stats.DatabaseOK = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query total accounts.
|
||||||
|
if s.db != nil {
|
||||||
|
if err := s.db.QueryRow("SELECT COUNT(*) FROM users").Scan(&stats.TotalAccounts); err != nil {
|
||||||
|
s.logger.Warn("Dashboard: failed to count users", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query total characters.
|
||||||
|
if s.db != nil {
|
||||||
|
if err := s.db.QueryRow("SELECT COUNT(*) FROM characters").Scan(&stats.TotalCharacters); err != nil {
|
||||||
|
s.logger.Warn("Dashboard: failed to count characters", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query channel info from servers table.
|
||||||
|
if s.db != nil {
|
||||||
|
rows, err := s.db.Query("SELECT server_id, current_players, world_name, land FROM servers ORDER BY server_id")
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Warn("Dashboard: failed to query servers", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
defer func() { _ = rows.Close() }()
|
||||||
|
for rows.Next() {
|
||||||
|
var serverID, players, land int
|
||||||
|
var worldName *string
|
||||||
|
if err := rows.Scan(&serverID, &players, &worldName, &land); err != nil {
|
||||||
|
s.logger.Warn("Dashboard: failed to scan server row", zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := "Channel"
|
||||||
|
if worldName != nil {
|
||||||
|
name = *worldName
|
||||||
|
}
|
||||||
|
ch := ChannelInfo{
|
||||||
|
Name: name,
|
||||||
|
Port: 54000 + serverID,
|
||||||
|
Players: players,
|
||||||
|
}
|
||||||
|
stats.Channels = append(stats.Channels, ch)
|
||||||
|
stats.OnlinePlayers += players
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(stats); err != nil {
|
||||||
|
s.logger.Error("Dashboard: failed to encode stats", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatDuration produces a human-readable duration string like "2d 5h 32m 10s".
|
||||||
|
func formatDuration(d time.Duration) string {
|
||||||
|
days := int(d.Hours()) / 24
|
||||||
|
hours := int(d.Hours()) % 24
|
||||||
|
minutes := int(d.Minutes()) % 60
|
||||||
|
seconds := int(d.Seconds()) % 60
|
||||||
|
|
||||||
|
if days > 0 {
|
||||||
|
return fmt.Sprintf("%dd %dh %dm %ds", days, hours, minutes, seconds)
|
||||||
|
}
|
||||||
|
if hours > 0 {
|
||||||
|
return fmt.Sprintf("%dh %dm %ds", hours, minutes, seconds)
|
||||||
|
}
|
||||||
|
if minutes > 0 {
|
||||||
|
return fmt.Sprintf("%dm %ds", minutes, seconds)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%ds", seconds)
|
||||||
|
}
|
||||||
145
server/api/dashboard.html
Normal file
145
server/api/dashboard.html
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Erupe Dashboard</title>
|
||||||
|
<style>
|
||||||
|
*{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#1a1a2e;color:#e0e0e0;min-height:100vh;padding:2rem}
|
||||||
|
.header{text-align:center;margin-bottom:2rem}
|
||||||
|
.header h1{font-size:2rem;color:#e94560;margin-bottom:.25rem}
|
||||||
|
.header .version{font-size:.9rem;color:#888}
|
||||||
|
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:1rem;margin-bottom:2rem}
|
||||||
|
.card{background:#16213e;border-radius:10px;padding:1.25rem;text-align:center;box-shadow:0 4px 16px rgba(0,0,0,.3)}
|
||||||
|
.card .label{font-size:.8rem;color:#888;text-transform:uppercase;letter-spacing:.05em;margin-bottom:.5rem}
|
||||||
|
.card .value{font-size:1.75rem;font-weight:700;color:#4ecdc4}
|
||||||
|
.card .value.accent{color:#e94560}
|
||||||
|
.card .value.ok{color:#4ecdc4}
|
||||||
|
.card .value.fail{color:#e94560}
|
||||||
|
.channels{background:#16213e;border-radius:10px;padding:1.5rem;box-shadow:0 4px 16px rgba(0,0,0,.3);margin-bottom:1.5rem}
|
||||||
|
.channels h2{font-size:1.1rem;color:#e94560;margin-bottom:1rem}
|
||||||
|
table{width:100%;border-collapse:collapse}
|
||||||
|
th{text-align:left;font-size:.75rem;color:#888;text-transform:uppercase;letter-spacing:.05em;padding:.5rem .75rem;border-bottom:1px solid #0f3460}
|
||||||
|
td{padding:.6rem .75rem;border-bottom:1px solid rgba(15,52,96,.5)}
|
||||||
|
.dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:.5rem;vertical-align:middle}
|
||||||
|
.dot.active{background:#4ecdc4}
|
||||||
|
.dot.empty{background:#555}
|
||||||
|
.footer{text-align:center;font-size:.8rem;color:#555}
|
||||||
|
.no-channels{color:#888;font-style:italic;padding:1rem 0}
|
||||||
|
.error-banner{background:#e94560;color:#fff;text-align:center;padding:.75rem;border-radius:8px;margin-bottom:1rem;display:none}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>Erupe Dashboard</h1>
|
||||||
|
<div class="version" id="version">Loading...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="error-banner" class="error-banner">Failed to fetch server stats</div>
|
||||||
|
|
||||||
|
<div class="cards">
|
||||||
|
<div class="card">
|
||||||
|
<div class="label">Uptime</div>
|
||||||
|
<div class="value" id="uptime" style="font-size:1.25rem">--</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="label">Online Players</div>
|
||||||
|
<div class="value accent" id="online-players">--</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="label">Total Accounts</div>
|
||||||
|
<div class="value" id="total-accounts">--</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="label">Total Characters</div>
|
||||||
|
<div class="value" id="total-characters">--</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="label">Database</div>
|
||||||
|
<div class="value" id="db-status">--</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="channels">
|
||||||
|
<h2>Channels</h2>
|
||||||
|
<div id="channels-content">
|
||||||
|
<div class="no-channels">Loading...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
Last updated: <span id="last-updated">never</span> | Auto-refreshes every 5s
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var lastUpdated = null;
|
||||||
|
|
||||||
|
function updateStats() {
|
||||||
|
fetch("/api/dashboard/stats")
|
||||||
|
.then(function(r) {
|
||||||
|
if (!r.ok) throw new Error("HTTP " + r.status);
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
|
.then(function(d) {
|
||||||
|
document.getElementById("error-banner").style.display = "none";
|
||||||
|
document.getElementById("version").textContent = d.serverVersion + " - " + d.clientMode;
|
||||||
|
document.getElementById("uptime").textContent = d.uptime;
|
||||||
|
document.getElementById("online-players").textContent = d.onlinePlayers;
|
||||||
|
document.getElementById("total-accounts").textContent = d.totalAccounts;
|
||||||
|
document.getElementById("total-characters").textContent = d.totalCharacters;
|
||||||
|
|
||||||
|
var dbEl = document.getElementById("db-status");
|
||||||
|
if (d.databaseOK) {
|
||||||
|
dbEl.textContent = "OK";
|
||||||
|
dbEl.className = "value ok";
|
||||||
|
} else {
|
||||||
|
dbEl.textContent = "DOWN";
|
||||||
|
dbEl.className = "value fail";
|
||||||
|
}
|
||||||
|
|
||||||
|
var container = document.getElementById("channels-content");
|
||||||
|
if (!d.channels || d.channels.length === 0) {
|
||||||
|
container.innerHTML = '<div class="no-channels">No channels registered</div>';
|
||||||
|
} else {
|
||||||
|
var html = '<table><thead><tr><th>Status</th><th>Name</th><th>Port</th><th>Players</th></tr></thead><tbody>';
|
||||||
|
for (var i = 0; i < d.channels.length; i++) {
|
||||||
|
var ch = d.channels[i];
|
||||||
|
var dotClass = ch.players > 0 ? "active" : "empty";
|
||||||
|
html += '<tr><td><span class="dot ' + dotClass + '"></span></td>';
|
||||||
|
html += '<td>' + escapeHtml(ch.name) + '</td>';
|
||||||
|
html += '<td>' + ch.port + '</td>';
|
||||||
|
html += '<td>' + ch.players + '</td></tr>';
|
||||||
|
}
|
||||||
|
html += '</tbody></table>';
|
||||||
|
container.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastUpdated = new Date();
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
document.getElementById("error-banner").style.display = "block";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTimer() {
|
||||||
|
if (lastUpdated) {
|
||||||
|
var ago = Math.floor((Date.now() - lastUpdated.getTime()) / 1000);
|
||||||
|
document.getElementById("last-updated").textContent = ago + "s ago";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(s) {
|
||||||
|
var div = document.createElement("div");
|
||||||
|
div.appendChild(document.createTextNode(s));
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStats();
|
||||||
|
setInterval(updateStats, 5000);
|
||||||
|
setInterval(updateTimer, 1000);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -3,16 +3,16 @@ package api
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APICharacterRepository implements APICharacterRepo with PostgreSQL.
|
// APICharacterRepository implements APICharacterRepo with PostgreSQL.
|
||||||
type APICharacterRepository struct {
|
type APICharacterRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPICharacterRepository creates a new APICharacterRepository.
|
// NewAPICharacterRepository creates a new APICharacterRepository.
|
||||||
func NewAPICharacterRepository(db *sqlx.DB) *APICharacterRepository {
|
func NewAPICharacterRepository(db *dbutil.DB) *APICharacterRepository {
|
||||||
return &APICharacterRepository{db: db}
|
return &APICharacterRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
type apiEventRepository struct {
|
type apiEventRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPIEventRepository creates an APIEventRepo backed by PostgreSQL.
|
// NewAPIEventRepository creates an APIEventRepo backed by PostgreSQL.
|
||||||
func NewAPIEventRepository(db *sqlx.DB) APIEventRepo {
|
func NewAPIEventRepository(db *dbutil.DB) APIEventRepo {
|
||||||
return &apiEventRepository{db: db}
|
return &apiEventRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ package api
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APISessionRepository implements APISessionRepo with PostgreSQL.
|
// APISessionRepository implements APISessionRepo with PostgreSQL.
|
||||||
type APISessionRepository struct {
|
type APISessionRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPISessionRepository creates a new APISessionRepository.
|
// NewAPISessionRepository creates a new APISessionRepository.
|
||||||
func NewAPISessionRepository(db *sqlx.DB) *APISessionRepository {
|
func NewAPISessionRepository(db *dbutil.DB) *APISessionRepository {
|
||||||
return &APISessionRepository{db: db}
|
return &APISessionRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIUserRepository implements APIUserRepo with PostgreSQL.
|
// APIUserRepository implements APIUserRepo with PostgreSQL.
|
||||||
type APIUserRepository struct {
|
type APIUserRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPIUserRepository creates a new APIUserRepository.
|
// NewAPIUserRepository creates a new APIUserRepository.
|
||||||
func NewAPIUserRepository(db *sqlx.DB) *APIUserRepository {
|
func NewAPIUserRepository(db *dbutil.DB) *APIUserRepository {
|
||||||
return &APIUserRepository{db: db}
|
return &APIUserRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -170,8 +170,8 @@ func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
if itemType == 17 {
|
if itemType == 17 {
|
||||||
if err := addPointNetcafe(s, int(quantity)); err != nil {
|
if err := addPointNetcafe(s, int(quantity)); err != nil {
|
||||||
s.logger.Error("Failed to add cafe bonus netcafe points", zap.Error(err))
|
s.logger.Error("Failed to add cafe bonus netcafe points", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := s.server.cafeRepo.AcceptBonus(cbID, s.charID); err != nil {
|
if err := s.server.cafeRepo.AcceptBonus(cbID, s.charID); err != nil {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
cfg "erupe-ce/config"
|
|
||||||
"erupe-ce/common/mhfcourse"
|
|
||||||
"erupe-ce/network/mhfpacket"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"erupe-ce/common/mhfcourse"
|
||||||
|
cfg "erupe-ce/config"
|
||||||
|
"erupe-ce/network/mhfpacket"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -140,8 +140,8 @@ func handleMsgMhfAcquireDistItem(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
switch item.ItemType {
|
switch item.ItemType {
|
||||||
case 17:
|
case 17:
|
||||||
if err := addPointNetcafe(s, int(item.Quantity)); err != nil {
|
if err := addPointNetcafe(s, int(item.Quantity)); err != nil {
|
||||||
s.logger.Error("Failed to add dist item netcafe points", zap.Error(err))
|
s.logger.Error("Failed to add dist item netcafe points", zap.Error(err))
|
||||||
}
|
}
|
||||||
case 19:
|
case 19:
|
||||||
if err := s.server.userRepo.AddPremiumCoins(s.userID, item.Quantity); err != nil {
|
if err := s.server.userRepo.AddPremiumCoins(s.userID, item.Quantity); err != nil {
|
||||||
s.logger.Error("Failed to update gacha premium", zap.Error(err))
|
s.logger.Error("Failed to update gacha premium", zap.Error(err))
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
cfg "erupe-ce/config"
|
||||||
"erupe-ce/network/mhfpacket"
|
"erupe-ce/network/mhfpacket"
|
||||||
"time"
|
"time"
|
||||||
cfg "erupe-ce/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHandleMsgMhfGetUdInfo(t *testing.T) {
|
func TestHandleMsgMhfGetUdInfo(t *testing.T) {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"erupe-ce/common/byteframe"
|
"erupe-ce/common/byteframe"
|
||||||
"erupe-ce/network/mhfpacket"
|
|
||||||
"erupe-ce/common/stringsupport"
|
"erupe-ce/common/stringsupport"
|
||||||
|
"erupe-ce/network/mhfpacket"
|
||||||
)
|
)
|
||||||
|
|
||||||
// --- handleMsgMhfOperateGuild tests ---
|
// --- handleMsgMhfOperateGuild tests ---
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ func TestPostGuildScout_Success(t *testing.T) {
|
|||||||
func TestPostGuildScout_AlreadyInvited(t *testing.T) {
|
func TestPostGuildScout_AlreadyInvited(t *testing.T) {
|
||||||
server := createMockServer()
|
server := createMockServer()
|
||||||
guildMock := &mockGuildRepo{
|
guildMock := &mockGuildRepo{
|
||||||
membership: &GuildMember{GuildID: 10, Recruiter: true},
|
membership: &GuildMember{GuildID: 10, Recruiter: true},
|
||||||
createAppErr: ErrAlreadyInvited,
|
createAppErr: ErrAlreadyInvited,
|
||||||
}
|
}
|
||||||
guildMock.guild = &Guild{ID: 10, Name: "TestGuild"}
|
guildMock.guild = &Guild{ID: 10, Name: "TestGuild"}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"erupe-ce/common/byteframe"
|
"erupe-ce/common/byteframe"
|
||||||
"erupe-ce/network/mhfpacket"
|
|
||||||
cfg "erupe-ce/config"
|
cfg "erupe-ce/config"
|
||||||
|
"erupe-ce/network/mhfpacket"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHandleMsgMhfLoadLegendDispatch(t *testing.T) {
|
func TestHandleMsgMhfLoadLegendDispatch(t *testing.T) {
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AchievementRepository centralizes all database access for the achievements table.
|
// AchievementRepository centralizes all database access for the achievements table.
|
||||||
type AchievementRepository struct {
|
type AchievementRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAchievementRepository creates a new AchievementRepository.
|
// NewAchievementRepository creates a new AchievementRepository.
|
||||||
func NewAchievementRepository(db *sqlx.DB) *AchievementRepository {
|
func NewAchievementRepository(db *dbutil.DB) *AchievementRepository {
|
||||||
return &AchievementRepository{db: db}
|
return &AchievementRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -11,7 +12,7 @@ func setupAchievementRepo(t *testing.T) (*AchievementRepository, *sqlx.DB, uint3
|
|||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
userID := CreateTestUser(t, db, "ach_test_user")
|
userID := CreateTestUser(t, db, "ach_test_user")
|
||||||
charID := CreateTestCharacter(t, db, userID, "AchChar")
|
charID := CreateTestCharacter(t, db, userID, "AchChar")
|
||||||
repo := NewAchievementRepository(db)
|
repo := NewAchievementRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, charID
|
return repo, db, charID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CafeRepository centralizes all database access for cafe-related tables.
|
// CafeRepository centralizes all database access for cafe-related tables.
|
||||||
type CafeRepository struct {
|
type CafeRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCafeRepository creates a new CafeRepository.
|
// NewCafeRepository creates a new CafeRepository.
|
||||||
func NewCafeRepository(db *sqlx.DB) *CafeRepository {
|
func NewCafeRepository(db *dbutil.DB) *CafeRepository {
|
||||||
return &CafeRepository{db: db}
|
return &CafeRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -11,7 +12,7 @@ func setupCafeRepo(t *testing.T) (*CafeRepository, *sqlx.DB, uint32) {
|
|||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
userID := CreateTestUser(t, db, "cafe_test_user")
|
userID := CreateTestUser(t, db, "cafe_test_user")
|
||||||
charID := CreateTestCharacter(t, db, userID, "CafeChar")
|
charID := CreateTestCharacter(t, db, userID, "CafeChar")
|
||||||
repo := NewCafeRepository(db)
|
repo := NewCafeRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, charID
|
return repo, db, charID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CharacterRepository centralizes all database access for the characters table.
|
// CharacterRepository centralizes all database access for the characters table.
|
||||||
type CharacterRepository struct {
|
type CharacterRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCharacterRepository creates a new CharacterRepository.
|
// NewCharacterRepository creates a new CharacterRepository.
|
||||||
func NewCharacterRepository(db *sqlx.DB) *CharacterRepository {
|
func NewCharacterRepository(db *dbutil.DB) *CharacterRepository {
|
||||||
return &CharacterRepository{db: db}
|
return &CharacterRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ func setupCharRepo(t *testing.T) (*CharacterRepository, *sqlx.DB, uint32) {
|
|||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
userID := CreateTestUser(t, db, "repo_test_user")
|
userID := CreateTestUser(t, db, "repo_test_user")
|
||||||
charID := CreateTestCharacter(t, db, userID, "RepoChar")
|
charID := CreateTestCharacter(t, db, userID, "RepoChar")
|
||||||
repo := NewCharacterRepository(db)
|
repo := NewCharacterRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, charID
|
return repo, db, charID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DistributionRepository centralizes all database access for the distribution,
|
// DistributionRepository centralizes all database access for the distribution,
|
||||||
// distribution_items, and distributions_accepted tables.
|
// distribution_items, and distributions_accepted tables.
|
||||||
type DistributionRepository struct {
|
type DistributionRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDistributionRepository creates a new DistributionRepository.
|
// NewDistributionRepository creates a new DistributionRepository.
|
||||||
func NewDistributionRepository(db *sqlx.DB) *DistributionRepository {
|
func NewDistributionRepository(db *dbutil.DB) *DistributionRepository {
|
||||||
return &DistributionRepository{db: db}
|
return &DistributionRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -11,7 +12,7 @@ func setupDistributionRepo(t *testing.T) (*DistributionRepository, *sqlx.DB, uin
|
|||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
userID := CreateTestUser(t, db, "dist_test_user")
|
userID := CreateTestUser(t, db, "dist_test_user")
|
||||||
charID := CreateTestCharacter(t, db, userID, "DistChar")
|
charID := CreateTestCharacter(t, db, userID, "DistChar")
|
||||||
repo := NewDistributionRepository(db)
|
repo := NewDistributionRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, charID
|
return repo, db, charID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DivaRepository centralizes all database access for diva defense events.
|
// DivaRepository centralizes all database access for diva defense events.
|
||||||
type DivaRepository struct {
|
type DivaRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDivaRepository creates a new DivaRepository.
|
// NewDivaRepository creates a new DivaRepository.
|
||||||
func NewDivaRepository(db *sqlx.DB) *DivaRepository {
|
func NewDivaRepository(db *dbutil.DB) *DivaRepository {
|
||||||
return &DivaRepository{db: db}
|
return &DivaRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -9,7 +10,7 @@ import (
|
|||||||
func setupDivaRepo(t *testing.T) (*DivaRepository, *sqlx.DB) {
|
func setupDivaRepo(t *testing.T) (*DivaRepository, *sqlx.DB) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
repo := NewDivaRepository(db)
|
repo := NewDivaRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db
|
return repo, db
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EventQuest represents a row from the event_quests table.
|
// EventQuest represents a row from the event_quests table.
|
||||||
@@ -22,11 +22,11 @@ type EventQuest struct {
|
|||||||
|
|
||||||
// EventRepository centralizes all database access for event-related tables.
|
// EventRepository centralizes all database access for event-related tables.
|
||||||
type EventRepository struct {
|
type EventRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEventRepository creates a new EventRepository.
|
// NewEventRepository creates a new EventRepository.
|
||||||
func NewEventRepository(db *sqlx.DB) *EventRepository {
|
func NewEventRepository(db *dbutil.DB) *EventRepository {
|
||||||
return &EventRepository{db: db}
|
return &EventRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ import (
|
|||||||
func setupEventRepo(t *testing.T) (*EventRepository, *sqlx.DB) {
|
func setupEventRepo(t *testing.T) (*EventRepository, *sqlx.DB) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
repo := NewEventRepository(db)
|
repo := NewEventRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db
|
return repo, db
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FestaRepository centralizes all database access for festa-related tables
|
// FestaRepository centralizes all database access for festa-related tables
|
||||||
// (events, festa_registrations, festa_submissions, festa_prizes, festa_prizes_accepted, festa_trials, guild_characters).
|
// (events, festa_registrations, festa_submissions, festa_prizes, festa_prizes_accepted, festa_trials, guild_characters).
|
||||||
type FestaRepository struct {
|
type FestaRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFestaRepository creates a new FestaRepository.
|
// NewFestaRepository creates a new FestaRepository.
|
||||||
func NewFestaRepository(db *sqlx.DB) *FestaRepository {
|
func NewFestaRepository(db *dbutil.DB) *FestaRepository {
|
||||||
return &FestaRepository{db: db}
|
return &FestaRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ func setupFestaRepo(t *testing.T) (*FestaRepository, *sqlx.DB, uint32, uint32) {
|
|||||||
userID := CreateTestUser(t, db, "festa_test_user")
|
userID := CreateTestUser(t, db, "festa_test_user")
|
||||||
charID := CreateTestCharacter(t, db, userID, "FestaChar")
|
charID := CreateTestCharacter(t, db, userID, "FestaChar")
|
||||||
guildID := CreateTestGuild(t, db, charID, "FestaGuild")
|
guildID := CreateTestGuild(t, db, charID, "FestaGuild")
|
||||||
repo := NewFestaRepository(db)
|
repo := NewFestaRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, charID, guildID
|
return repo, db, charID, guildID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GachaRepository centralizes all database access for gacha-related tables
|
// GachaRepository centralizes all database access for gacha-related tables
|
||||||
// (gacha_shop, gacha_entries, gacha_items, gacha_stepup, gacha_box).
|
// (gacha_shop, gacha_entries, gacha_items, gacha_stepup, gacha_box).
|
||||||
type GachaRepository struct {
|
type GachaRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGachaRepository creates a new GachaRepository.
|
// NewGachaRepository creates a new GachaRepository.
|
||||||
func NewGachaRepository(db *sqlx.DB) *GachaRepository {
|
func NewGachaRepository(db *dbutil.DB) *GachaRepository {
|
||||||
return &GachaRepository{db: db}
|
return &GachaRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -13,7 +14,7 @@ func setupGachaRepo(t *testing.T) (*GachaRepository, *sqlx.DB, uint32) {
|
|||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
userID := CreateTestUser(t, db, "gacha_test_user")
|
userID := CreateTestUser(t, db, "gacha_test_user")
|
||||||
charID := CreateTestCharacter(t, db, userID, "GachaChar")
|
charID := CreateTestCharacter(t, db, userID, "GachaChar")
|
||||||
repo := NewGachaRepository(db)
|
repo := NewGachaRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, charID
|
return repo, db, charID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GoocooRepository centralizes all database access for the goocoo table.
|
// GoocooRepository centralizes all database access for the goocoo table.
|
||||||
type GoocooRepository struct {
|
type GoocooRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGoocooRepository creates a new GoocooRepository.
|
// NewGoocooRepository creates a new GoocooRepository.
|
||||||
func NewGoocooRepository(db *sqlx.DB) *GoocooRepository {
|
func NewGoocooRepository(db *dbutil.DB) *GoocooRepository {
|
||||||
return &GoocooRepository{db: db}
|
return &GoocooRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -11,7 +12,7 @@ func setupGoocooRepo(t *testing.T) (*GoocooRepository, *sqlx.DB, uint32) {
|
|||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
userID := CreateTestUser(t, db, "goocoo_test_user")
|
userID := CreateTestUser(t, db, "goocoo_test_user")
|
||||||
charID := CreateTestCharacter(t, db, userID, "GoocooChar")
|
charID := CreateTestCharacter(t, db, userID, "GoocooChar")
|
||||||
repo := NewGoocooRepository(db)
|
repo := NewGoocooRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, charID
|
return repo, db, charID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,19 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GuildRepository centralizes all database access for guild-related tables
|
// GuildRepository centralizes all database access for guild-related tables
|
||||||
// (guilds, guild_characters, guild_applications).
|
// (guilds, guild_characters, guild_applications).
|
||||||
type GuildRepository struct {
|
type GuildRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGuildRepository creates a new GuildRepository.
|
// NewGuildRepository creates a new GuildRepository.
|
||||||
func NewGuildRepository(db *sqlx.DB) *GuildRepository {
|
func NewGuildRepository(db *dbutil.DB) *GuildRepository {
|
||||||
return &GuildRepository{db: db}
|
return &GuildRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,7 +14,7 @@ func setupGuildRepo(t *testing.T) (*GuildRepository, *sqlx.DB, uint32, uint32) {
|
|||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
userID := CreateTestUser(t, db, "guild_test_user")
|
userID := CreateTestUser(t, db, "guild_test_user")
|
||||||
charID := CreateTestCharacter(t, db, userID, "GuildLeader")
|
charID := CreateTestCharacter(t, db, userID, "GuildLeader")
|
||||||
repo := NewGuildRepository(db)
|
repo := NewGuildRepository(dbutil.Wrap(db))
|
||||||
guildID := CreateTestGuild(t, db, charID, "TestGuild")
|
guildID := CreateTestGuild(t, db, charID, "TestGuild")
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, guildID, charID
|
return repo, db, guildID, charID
|
||||||
@@ -82,7 +83,7 @@ func TestGetByCharIDNotFound(t *testing.T) {
|
|||||||
func TestCreate(t *testing.T) {
|
func TestCreate(t *testing.T) {
|
||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
defer TeardownTestDB(t, db)
|
defer TeardownTestDB(t, db)
|
||||||
repo := NewGuildRepository(db)
|
repo := NewGuildRepository(dbutil.Wrap(db))
|
||||||
userID := CreateTestUser(t, db, "create_guild_user")
|
userID := CreateTestUser(t, db, "create_guild_user")
|
||||||
charID := CreateTestCharacter(t, db, userID, "CreateLeader")
|
charID := CreateTestCharacter(t, db, userID, "CreateLeader")
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,17 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HouseRepository centralizes all database access for house-related tables
|
// HouseRepository centralizes all database access for house-related tables
|
||||||
// (user_binary house columns, warehouse, titles).
|
// (user_binary house columns, warehouse, titles).
|
||||||
type HouseRepository struct {
|
type HouseRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHouseRepository creates a new HouseRepository.
|
// NewHouseRepository creates a new HouseRepository.
|
||||||
func NewHouseRepository(db *sqlx.DB) *HouseRepository {
|
func NewHouseRepository(db *dbutil.DB) *HouseRepository {
|
||||||
return &HouseRepository{db: db}
|
return &HouseRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -12,7 +13,7 @@ func setupHouseRepo(t *testing.T) (*HouseRepository, *sqlx.DB, uint32) {
|
|||||||
userID := CreateTestUser(t, db, "house_test_user")
|
userID := CreateTestUser(t, db, "house_test_user")
|
||||||
charID := CreateTestCharacter(t, db, userID, "HouseChar")
|
charID := CreateTestCharacter(t, db, userID, "HouseChar")
|
||||||
CreateTestUserBinary(t, db, charID)
|
CreateTestUserBinary(t, db, charID)
|
||||||
repo := NewHouseRepository(db)
|
repo := NewHouseRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, charID
|
return repo, db, charID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MailRepository centralizes all database access for the mail table.
|
// MailRepository centralizes all database access for the mail table.
|
||||||
type MailRepository struct {
|
type MailRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMailRepository creates a new MailRepository.
|
// NewMailRepository creates a new MailRepository.
|
||||||
func NewMailRepository(db *sqlx.DB) *MailRepository {
|
func NewMailRepository(db *dbutil.DB) *MailRepository {
|
||||||
return &MailRepository{db: db}
|
return &MailRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -13,7 +14,7 @@ func setupMailRepo(t *testing.T) (*MailRepository, *sqlx.DB, uint32, uint32) {
|
|||||||
senderID := CreateTestCharacter(t, db, userID, "Sender")
|
senderID := CreateTestCharacter(t, db, userID, "Sender")
|
||||||
userID2 := CreateTestUser(t, db, "mail_recipient")
|
userID2 := CreateTestUser(t, db, "mail_recipient")
|
||||||
recipientID := CreateTestCharacter(t, db, userID2, "Recipient")
|
recipientID := CreateTestCharacter(t, db, userID2, "Recipient")
|
||||||
repo := NewMailRepository(db)
|
repo := NewMailRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, senderID, recipientID
|
return repo, db, senderID, recipientID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MercenaryRepository centralizes database access for mercenary/rasta/airou sequences and queries.
|
// MercenaryRepository centralizes database access for mercenary/rasta/airou sequences and queries.
|
||||||
type MercenaryRepository struct {
|
type MercenaryRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMercenaryRepository creates a new MercenaryRepository.
|
// NewMercenaryRepository creates a new MercenaryRepository.
|
||||||
func NewMercenaryRepository(db *sqlx.DB) *MercenaryRepository {
|
func NewMercenaryRepository(db *dbutil.DB) *MercenaryRepository {
|
||||||
return &MercenaryRepository{db: db}
|
return &MercenaryRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -12,7 +13,7 @@ func setupMercenaryRepo(t *testing.T) (*MercenaryRepository, *sqlx.DB, uint32, u
|
|||||||
userID := CreateTestUser(t, db, "merc_test_user")
|
userID := CreateTestUser(t, db, "merc_test_user")
|
||||||
charID := CreateTestCharacter(t, db, userID, "MercChar")
|
charID := CreateTestCharacter(t, db, userID, "MercChar")
|
||||||
guildID := CreateTestGuild(t, db, charID, "MercGuild")
|
guildID := CreateTestGuild(t, db, charID, "MercGuild")
|
||||||
repo := NewMercenaryRepository(db)
|
repo := NewMercenaryRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, charID, guildID
|
return repo, db, charID, guildID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MiscRepository centralizes database access for miscellaneous game tables.
|
// MiscRepository centralizes database access for miscellaneous game tables.
|
||||||
type MiscRepository struct {
|
type MiscRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMiscRepository creates a new MiscRepository.
|
// NewMiscRepository creates a new MiscRepository.
|
||||||
func NewMiscRepository(db *sqlx.DB) *MiscRepository {
|
func NewMiscRepository(db *dbutil.DB) *MiscRepository {
|
||||||
return &MiscRepository{db: db}
|
return &MiscRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -9,7 +10,7 @@ import (
|
|||||||
func setupMiscRepo(t *testing.T) (*MiscRepository, *sqlx.DB) {
|
func setupMiscRepo(t *testing.T) (*MiscRepository, *sqlx.DB) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
repo := NewMiscRepository(db)
|
repo := NewMiscRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db
|
return repo, db
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -304,15 +304,15 @@ type mockGuildRepo struct {
|
|||||||
deletedPostID uint32
|
deletedPostID uint32
|
||||||
|
|
||||||
// Alliance
|
// Alliance
|
||||||
alliance *GuildAlliance
|
alliance *GuildAlliance
|
||||||
getAllianceErr error
|
getAllianceErr error
|
||||||
createAllianceErr error
|
createAllianceErr error
|
||||||
deleteAllianceErr error
|
deleteAllianceErr error
|
||||||
removeAllyErr error
|
removeAllyErr error
|
||||||
setAllianceRecruitErr error
|
setAllianceRecruitErr error
|
||||||
deletedAllianceID uint32
|
deletedAllianceID uint32
|
||||||
removedAllyArgs []uint32
|
removedAllyArgs []uint32
|
||||||
allianceRecruitingSet *bool
|
allianceRecruitingSet *bool
|
||||||
|
|
||||||
// Cooking
|
// Cooking
|
||||||
meals []*GuildMeal
|
meals []*GuildMeal
|
||||||
@@ -552,28 +552,28 @@ func (m *mockGuildRepo) Create(_ uint32, _ string) (int32, error) { return 0, ni
|
|||||||
func (m *mockGuildRepo) CreateApplicationWithMail(_, _, _ uint32, _ GuildApplicationType, _, _ uint32, _, _ string) error {
|
func (m *mockGuildRepo) CreateApplicationWithMail(_, _, _ uint32, _ GuildApplicationType, _, _ uint32, _, _ string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (m *mockGuildRepo) CancelInvitation(_, _ uint32) error { return nil }
|
func (m *mockGuildRepo) CancelInvitation(_, _ uint32) error { return nil }
|
||||||
func (m *mockGuildRepo) ArrangeCharacters(_ []uint32) error { return nil }
|
func (m *mockGuildRepo) ArrangeCharacters(_ []uint32) error { return nil }
|
||||||
func (m *mockGuildRepo) GetItemBox(_ uint32) ([]byte, error) { return nil, nil }
|
func (m *mockGuildRepo) GetItemBox(_ uint32) ([]byte, error) { return nil, nil }
|
||||||
func (m *mockGuildRepo) SaveItemBox(_ uint32, _ []byte) error { return nil }
|
func (m *mockGuildRepo) SaveItemBox(_ uint32, _ []byte) error { return nil }
|
||||||
func (m *mockGuildRepo) SetRecruiting(_ uint32, _ bool) error { return nil }
|
func (m *mockGuildRepo) SetRecruiting(_ uint32, _ bool) error { return nil }
|
||||||
func (m *mockGuildRepo) SetPugiOutfits(_ uint32, _ uint32) error { return nil }
|
func (m *mockGuildRepo) SetPugiOutfits(_ uint32, _ uint32) error { return nil }
|
||||||
func (m *mockGuildRepo) SetRecruiter(_ uint32, _ bool) error { return nil }
|
func (m *mockGuildRepo) SetRecruiter(_ uint32, _ bool) error { return nil }
|
||||||
func (m *mockGuildRepo) AddMemberDailyRP(_ uint32, _ uint16) error { return nil }
|
func (m *mockGuildRepo) AddMemberDailyRP(_ uint32, _ uint16) error { return nil }
|
||||||
func (m *mockGuildRepo) ExchangeEventRP(_ uint32, _ uint16) (uint32, error) { return 0, nil }
|
func (m *mockGuildRepo) ExchangeEventRP(_ uint32, _ uint16) (uint32, error) { return 0, nil }
|
||||||
func (m *mockGuildRepo) AddRankRP(_ uint32, _ uint16) error { return nil }
|
func (m *mockGuildRepo) AddRankRP(_ uint32, _ uint16) error { return nil }
|
||||||
func (m *mockGuildRepo) AddEventRP(_ uint32, _ uint16) error { return nil }
|
func (m *mockGuildRepo) AddEventRP(_ uint32, _ uint16) error { return nil }
|
||||||
func (m *mockGuildRepo) GetRoomRP(_ uint32) (uint16, error) { return 0, nil }
|
func (m *mockGuildRepo) GetRoomRP(_ uint32) (uint16, error) { return 0, nil }
|
||||||
func (m *mockGuildRepo) SetRoomRP(_ uint32, _ uint16) error { return nil }
|
func (m *mockGuildRepo) SetRoomRP(_ uint32, _ uint16) error { return nil }
|
||||||
func (m *mockGuildRepo) AddRoomRP(_ uint32, _ uint16) error { return nil }
|
func (m *mockGuildRepo) AddRoomRP(_ uint32, _ uint16) error { return nil }
|
||||||
func (m *mockGuildRepo) SetRoomExpiry(_ uint32, _ time.Time) error { return nil }
|
func (m *mockGuildRepo) SetRoomExpiry(_ uint32, _ time.Time) error { return nil }
|
||||||
func (m *mockGuildRepo) UpdatePost(_ uint32, _, _ string) error { return nil }
|
func (m *mockGuildRepo) UpdatePost(_ uint32, _, _ string) error { return nil }
|
||||||
func (m *mockGuildRepo) UpdatePostStamp(_, _ uint32) error { return nil }
|
func (m *mockGuildRepo) UpdatePostStamp(_, _ uint32) error { return nil }
|
||||||
func (m *mockGuildRepo) GetPostLikedBy(_ uint32) (string, error) { return "", nil }
|
func (m *mockGuildRepo) GetPostLikedBy(_ uint32) (string, error) { return "", nil }
|
||||||
func (m *mockGuildRepo) SetPostLikedBy(_ uint32, _ string) error { return nil }
|
func (m *mockGuildRepo) SetPostLikedBy(_ uint32, _ string) error { return nil }
|
||||||
func (m *mockGuildRepo) CountNewPosts(_ uint32, _ time.Time) (int, error) { return 0, nil }
|
func (m *mockGuildRepo) CountNewPosts(_ uint32, _ time.Time) (int, error) { return 0, nil }
|
||||||
func (m *mockGuildRepo) ListAlliances() ([]*GuildAlliance, error) { return nil, nil }
|
func (m *mockGuildRepo) ListAlliances() ([]*GuildAlliance, error) { return nil, nil }
|
||||||
func (m *mockGuildRepo) ClearTreasureHunt(_ uint32) error { return nil }
|
func (m *mockGuildRepo) ClearTreasureHunt(_ uint32) error { return nil }
|
||||||
func (m *mockGuildRepo) InsertKillLog(_ uint32, _ int, _ uint8, _ time.Time) error { return nil }
|
func (m *mockGuildRepo) InsertKillLog(_ uint32, _ int, _ uint8, _ time.Time) error { return nil }
|
||||||
func (m *mockGuildRepo) ListInvitedCharacters(_ uint32) ([]*ScoutedCharacter, error) {
|
func (m *mockGuildRepo) ListInvitedCharacters(_ uint32) ([]*ScoutedCharacter, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -816,7 +816,6 @@ type mockGachaRepo struct {
|
|||||||
allEntries []GachaEntry
|
allEntries []GachaEntry
|
||||||
allEntriesErr error
|
allEntriesErr error
|
||||||
weightDivisor float64
|
weightDivisor float64
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockGachaRepo) GetEntryForTransaction(_ uint32, _ uint8) (uint8, uint16, int, error) {
|
func (m *mockGachaRepo) GetEntryForTransaction(_ uint32, _ uint8) (uint8, uint16, int, error) {
|
||||||
@@ -957,26 +956,26 @@ type mockTowerRepo struct {
|
|||||||
gemsErr error
|
gemsErr error
|
||||||
updatedGems string
|
updatedGems string
|
||||||
|
|
||||||
progress TenrouiraiProgressData
|
progress TenrouiraiProgressData
|
||||||
progressErr error
|
progressErr error
|
||||||
scores []TenrouiraiCharScore
|
scores []TenrouiraiCharScore
|
||||||
scoresErr error
|
scoresErr error
|
||||||
guildRP uint32
|
guildRP uint32
|
||||||
guildRPErr error
|
guildRPErr error
|
||||||
page int
|
page int
|
||||||
donated int
|
donated int
|
||||||
pageRPErr error
|
pageRPErr error
|
||||||
advanceErr error
|
advanceErr error
|
||||||
advanceCalled bool
|
advanceCalled bool
|
||||||
donateErr error
|
donateErr error
|
||||||
donatedRP uint16
|
donatedRP uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockTowerRepo) GetTowerData(_ uint32) (TowerData, error) { return m.towerData, m.towerDataErr }
|
func (m *mockTowerRepo) GetTowerData(_ uint32) (TowerData, error) { return m.towerData, m.towerDataErr }
|
||||||
func (m *mockTowerRepo) GetSkills(_ uint32) (string, error) { return m.skills, m.skillsErr }
|
func (m *mockTowerRepo) GetSkills(_ uint32) (string, error) { return m.skills, m.skillsErr }
|
||||||
func (m *mockTowerRepo) UpdateSkills(_ uint32, _ string, _ int32) error { return nil }
|
func (m *mockTowerRepo) UpdateSkills(_ uint32, _ string, _ int32) error { return nil }
|
||||||
func (m *mockTowerRepo) UpdateProgress(_ uint32, _, _, _, _ int32) error { return nil }
|
func (m *mockTowerRepo) UpdateProgress(_ uint32, _, _, _, _ int32) error { return nil }
|
||||||
func (m *mockTowerRepo) GetGems(_ uint32) (string, error) { return m.gems, m.gemsErr }
|
func (m *mockTowerRepo) GetGems(_ uint32) (string, error) { return m.gems, m.gemsErr }
|
||||||
func (m *mockTowerRepo) UpdateGems(_ uint32, gems string) error {
|
func (m *mockTowerRepo) UpdateGems(_ uint32, gems string) error {
|
||||||
m.updatedGems = gems
|
m.updatedGems = gems
|
||||||
return nil
|
return nil
|
||||||
@@ -1003,21 +1002,21 @@ func (m *mockTowerRepo) DonateGuildTowerRP(_ uint32, rp uint16) error {
|
|||||||
// --- mockFestaRepo ---
|
// --- mockFestaRepo ---
|
||||||
|
|
||||||
type mockFestaRepo struct {
|
type mockFestaRepo struct {
|
||||||
events []FestaEvent
|
events []FestaEvent
|
||||||
eventsErr error
|
eventsErr error
|
||||||
teamSouls uint32
|
teamSouls uint32
|
||||||
teamErr error
|
teamErr error
|
||||||
trials []FestaTrial
|
trials []FestaTrial
|
||||||
trialsErr error
|
trialsErr error
|
||||||
topGuild FestaGuildRanking
|
topGuild FestaGuildRanking
|
||||||
topErr error
|
topErr error
|
||||||
topWindow FestaGuildRanking
|
topWindow FestaGuildRanking
|
||||||
topWinErr error
|
topWinErr error
|
||||||
charSouls uint32
|
charSouls uint32
|
||||||
charErr error
|
charErr error
|
||||||
hasClaimed bool
|
hasClaimed bool
|
||||||
prizes []Prize
|
prizes []Prize
|
||||||
prizesErr error
|
prizesErr error
|
||||||
|
|
||||||
cleanupErr error
|
cleanupErr error
|
||||||
cleanupCalled bool
|
cleanupCalled bool
|
||||||
@@ -1035,8 +1034,8 @@ func (m *mockFestaRepo) InsertEvent(start uint32) error {
|
|||||||
m.insertedStart = start
|
m.insertedStart = start
|
||||||
return m.insertErr
|
return m.insertErr
|
||||||
}
|
}
|
||||||
func (m *mockFestaRepo) GetFestaEvents() ([]FestaEvent, error) { return m.events, m.eventsErr }
|
func (m *mockFestaRepo) GetFestaEvents() ([]FestaEvent, error) { return m.events, m.eventsErr }
|
||||||
func (m *mockFestaRepo) GetTeamSouls(_ string) (uint32, error) { return m.teamSouls, m.teamErr }
|
func (m *mockFestaRepo) GetTeamSouls(_ string) (uint32, error) { return m.teamSouls, m.teamErr }
|
||||||
func (m *mockFestaRepo) GetTrialsWithMonopoly() ([]FestaTrial, error) {
|
func (m *mockFestaRepo) GetTrialsWithMonopoly() ([]FestaTrial, error) {
|
||||||
return m.trials, m.trialsErr
|
return m.trials, m.trialsErr
|
||||||
}
|
}
|
||||||
@@ -1046,15 +1045,15 @@ func (m *mockFestaRepo) GetTopGuildForTrial(_ uint16) (FestaGuildRanking, error)
|
|||||||
func (m *mockFestaRepo) GetTopGuildInWindow(_, _ uint32) (FestaGuildRanking, error) {
|
func (m *mockFestaRepo) GetTopGuildInWindow(_, _ uint32) (FestaGuildRanking, error) {
|
||||||
return m.topWindow, m.topWinErr
|
return m.topWindow, m.topWinErr
|
||||||
}
|
}
|
||||||
func (m *mockFestaRepo) GetCharSouls(_ uint32) (uint32, error) { return m.charSouls, m.charErr }
|
func (m *mockFestaRepo) GetCharSouls(_ uint32) (uint32, error) { return m.charSouls, m.charErr }
|
||||||
func (m *mockFestaRepo) HasClaimedMainPrize(_ uint32) bool { return m.hasClaimed }
|
func (m *mockFestaRepo) HasClaimedMainPrize(_ uint32) bool { return m.hasClaimed }
|
||||||
func (m *mockFestaRepo) VoteTrial(_ uint32, _ uint32) error { return nil }
|
func (m *mockFestaRepo) VoteTrial(_ uint32, _ uint32) error { return nil }
|
||||||
func (m *mockFestaRepo) RegisterGuild(_ uint32, _ string) error { return nil }
|
func (m *mockFestaRepo) RegisterGuild(_ uint32, _ string) error { return nil }
|
||||||
func (m *mockFestaRepo) SubmitSouls(_, _ uint32, souls []uint16) error {
|
func (m *mockFestaRepo) SubmitSouls(_, _ uint32, souls []uint16) error {
|
||||||
m.submittedSouls = souls
|
m.submittedSouls = souls
|
||||||
return m.submitErr
|
return m.submitErr
|
||||||
}
|
}
|
||||||
func (m *mockFestaRepo) ClaimPrize(_ uint32, _ uint32) error { return nil }
|
func (m *mockFestaRepo) ClaimPrize(_ uint32, _ uint32) error { return nil }
|
||||||
func (m *mockFestaRepo) ListPrizes(_ uint32, _ string) ([]Prize, error) {
|
func (m *mockFestaRepo) ListPrizes(_ uint32, _ string) ([]Prize, error) {
|
||||||
return m.prizes, m.prizesErr
|
return m.prizes, m.prizesErr
|
||||||
}
|
}
|
||||||
@@ -1078,9 +1077,9 @@ type mockDivaRepo struct {
|
|||||||
eventsErr error
|
eventsErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockDivaRepo) DeleteEvents() error { return nil }
|
func (m *mockDivaRepo) DeleteEvents() error { return nil }
|
||||||
func (m *mockDivaRepo) InsertEvent(_ uint32) error { return nil }
|
func (m *mockDivaRepo) InsertEvent(_ uint32) error { return nil }
|
||||||
func (m *mockDivaRepo) GetEvents() ([]DivaEvent, error) { return m.events, m.eventsErr }
|
func (m *mockDivaRepo) GetEvents() ([]DivaEvent, error) { return m.events, m.eventsErr }
|
||||||
|
|
||||||
// --- mockEventRepo ---
|
// --- mockEventRepo ---
|
||||||
|
|
||||||
@@ -1122,20 +1121,20 @@ func (m *mockMiscRepo) UpsertTrendWeapon(_ uint16, _ uint8) error { return nil }
|
|||||||
// --- mockMercenaryRepo ---
|
// --- mockMercenaryRepo ---
|
||||||
|
|
||||||
type mockMercenaryRepo struct {
|
type mockMercenaryRepo struct {
|
||||||
nextRastaID uint32
|
nextRastaID uint32
|
||||||
rastaIDErr error
|
rastaIDErr error
|
||||||
nextAirouID uint32
|
nextAirouID uint32
|
||||||
airouIDErr error
|
airouIDErr error
|
||||||
loans []MercenaryLoan
|
loans []MercenaryLoan
|
||||||
loansErr error
|
loansErr error
|
||||||
catUsages []GuildHuntCatUsage
|
catUsages []GuildHuntCatUsage
|
||||||
catUsagesErr error
|
catUsagesErr error
|
||||||
guildAirou [][]byte
|
guildAirou [][]byte
|
||||||
guildAirouErr error
|
guildAirouErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockMercenaryRepo) NextRastaID() (uint32, error) { return m.nextRastaID, m.rastaIDErr }
|
func (m *mockMercenaryRepo) NextRastaID() (uint32, error) { return m.nextRastaID, m.rastaIDErr }
|
||||||
func (m *mockMercenaryRepo) NextAirouID() (uint32, error) { return m.nextAirouID, m.airouIDErr }
|
func (m *mockMercenaryRepo) NextAirouID() (uint32, error) { return m.nextAirouID, m.airouIDErr }
|
||||||
func (m *mockMercenaryRepo) GetMercenaryLoans(_ uint32) ([]MercenaryLoan, error) {
|
func (m *mockMercenaryRepo) GetMercenaryLoans(_ uint32) ([]MercenaryLoan, error) {
|
||||||
return m.loans, m.loansErr
|
return m.loans, m.loansErr
|
||||||
}
|
}
|
||||||
@@ -1149,17 +1148,17 @@ func (m *mockMercenaryRepo) GetGuildAirou(_ uint32) ([][]byte, error) {
|
|||||||
// --- mockCafeRepo ---
|
// --- mockCafeRepo ---
|
||||||
|
|
||||||
type mockCafeRepo struct {
|
type mockCafeRepo struct {
|
||||||
bonuses []CafeBonus
|
bonuses []CafeBonus
|
||||||
bonusesErr error
|
bonusesErr error
|
||||||
claimable []CafeBonus
|
claimable []CafeBonus
|
||||||
claimableErr error
|
claimableErr error
|
||||||
bonusItemType uint32
|
bonusItemType uint32
|
||||||
bonusItemQty uint32
|
bonusItemQty uint32
|
||||||
bonusItemErr error
|
bonusItemErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockCafeRepo) ResetAccepted(_ uint32) error { return nil }
|
func (m *mockCafeRepo) ResetAccepted(_ uint32) error { return nil }
|
||||||
func (m *mockCafeRepo) GetBonuses(_ uint32) ([]CafeBonus, error) { return m.bonuses, m.bonusesErr }
|
func (m *mockCafeRepo) GetBonuses(_ uint32) ([]CafeBonus, error) { return m.bonuses, m.bonusesErr }
|
||||||
func (m *mockCafeRepo) GetClaimable(_ uint32, _ int64) ([]CafeBonus, error) {
|
func (m *mockCafeRepo) GetClaimable(_ uint32, _ int64) ([]CafeBonus, error) {
|
||||||
return m.claimable, m.claimableErr
|
return m.claimable, m.claimableErr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RengokuRepository centralizes all database access for the rengoku_score table.
|
// RengokuRepository centralizes all database access for the rengoku_score table.
|
||||||
type RengokuRepository struct {
|
type RengokuRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRengokuRepository creates a new RengokuRepository.
|
// NewRengokuRepository creates a new RengokuRepository.
|
||||||
func NewRengokuRepository(db *sqlx.DB) *RengokuRepository {
|
func NewRengokuRepository(db *dbutil.DB) *RengokuRepository {
|
||||||
return &RengokuRepository{db: db}
|
return &RengokuRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -12,7 +13,7 @@ func setupRengokuRepo(t *testing.T) (*RengokuRepository, *sqlx.DB, uint32, uint3
|
|||||||
userID := CreateTestUser(t, db, "rengoku_test_user")
|
userID := CreateTestUser(t, db, "rengoku_test_user")
|
||||||
charID := CreateTestCharacter(t, db, userID, "RengokuChar")
|
charID := CreateTestCharacter(t, db, userID, "RengokuChar")
|
||||||
guildID := CreateTestGuild(t, db, charID, "RengokuGuild")
|
guildID := CreateTestGuild(t, db, charID, "RengokuGuild")
|
||||||
repo := NewRengokuRepository(db)
|
repo := NewRengokuRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, charID, guildID
|
return repo, db, charID, guildID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ScenarioRepository centralizes all database access for the scenario_counter table.
|
// ScenarioRepository centralizes all database access for the scenario_counter table.
|
||||||
type ScenarioRepository struct {
|
type ScenarioRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewScenarioRepository creates a new ScenarioRepository.
|
// NewScenarioRepository creates a new ScenarioRepository.
|
||||||
func NewScenarioRepository(db *sqlx.DB) *ScenarioRepository {
|
func NewScenarioRepository(db *dbutil.DB) *ScenarioRepository {
|
||||||
return &ScenarioRepository{db: db}
|
return &ScenarioRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -9,7 +10,7 @@ import (
|
|||||||
func setupScenarioRepo(t *testing.T) (*ScenarioRepository, *sqlx.DB) {
|
func setupScenarioRepo(t *testing.T) (*ScenarioRepository, *sqlx.DB) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
repo := NewScenarioRepository(db)
|
repo := NewScenarioRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db
|
return repo, db
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SessionRepository centralizes all database access for sign_sessions and servers tables.
|
// SessionRepository centralizes all database access for sign_sessions and servers tables.
|
||||||
type SessionRepository struct {
|
type SessionRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSessionRepository creates a new SessionRepository.
|
// NewSessionRepository creates a new SessionRepository.
|
||||||
func NewSessionRepository(db *sqlx.DB) *SessionRepository {
|
func NewSessionRepository(db *dbutil.DB) *SessionRepository {
|
||||||
return &SessionRepository{db: db}
|
return &SessionRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -13,7 +14,7 @@ func setupSessionRepo(t *testing.T) (*SessionRepository, *sqlx.DB, uint32, uint3
|
|||||||
charID := CreateTestCharacter(t, db, userID, "SessionChar")
|
charID := CreateTestCharacter(t, db, userID, "SessionChar")
|
||||||
token := "test_token_12345"
|
token := "test_token_12345"
|
||||||
sessionID := CreateTestSignSession(t, db, userID, token)
|
sessionID := CreateTestSignSession(t, db, userID, token)
|
||||||
repo := NewSessionRepository(db)
|
repo := NewSessionRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, userID, charID, sessionID, token
|
return repo, db, userID, charID, sessionID, token
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ShopRepository centralizes all database access for shop-related tables.
|
// ShopRepository centralizes all database access for shop-related tables.
|
||||||
type ShopRepository struct {
|
type ShopRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewShopRepository creates a new ShopRepository.
|
// NewShopRepository creates a new ShopRepository.
|
||||||
func NewShopRepository(db *sqlx.DB) *ShopRepository {
|
func NewShopRepository(db *dbutil.DB) *ShopRepository {
|
||||||
return &ShopRepository{db: db}
|
return &ShopRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -11,7 +12,7 @@ func setupShopRepo(t *testing.T) (*ShopRepository, *sqlx.DB, uint32) {
|
|||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
userID := CreateTestUser(t, db, "shop_test_user")
|
userID := CreateTestUser(t, db, "shop_test_user")
|
||||||
charID := CreateTestCharacter(t, db, userID, "ShopChar")
|
charID := CreateTestCharacter(t, db, userID, "ShopChar")
|
||||||
repo := NewShopRepository(db)
|
repo := NewShopRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, charID
|
return repo, db, charID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StampRepository centralizes all database access for the stamps table.
|
// StampRepository centralizes all database access for the stamps table.
|
||||||
type StampRepository struct {
|
type StampRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStampRepository creates a new StampRepository.
|
// NewStampRepository creates a new StampRepository.
|
||||||
func NewStampRepository(db *sqlx.DB) *StampRepository {
|
func NewStampRepository(db *dbutil.DB) *StampRepository {
|
||||||
return &StampRepository{db: db}
|
return &StampRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ func setupStampRepo(t *testing.T) (*StampRepository, *sqlx.DB, uint32) {
|
|||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
userID := CreateTestUser(t, db, "stamp_test_user")
|
userID := CreateTestUser(t, db, "stamp_test_user")
|
||||||
charID := CreateTestCharacter(t, db, userID, "StampChar")
|
charID := CreateTestCharacter(t, db, userID, "StampChar")
|
||||||
repo := NewStampRepository(db)
|
repo := NewStampRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, charID
|
return repo, db, charID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,17 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TowerRepository centralizes all database access for tower-related tables
|
// TowerRepository centralizes all database access for tower-related tables
|
||||||
// (tower, guilds tower columns, guild_characters tower columns).
|
// (tower, guilds tower columns, guild_characters tower columns).
|
||||||
type TowerRepository struct {
|
type TowerRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTowerRepository creates a new TowerRepository.
|
// NewTowerRepository creates a new TowerRepository.
|
||||||
func NewTowerRepository(db *sqlx.DB) *TowerRepository {
|
func NewTowerRepository(db *dbutil.DB) *TowerRepository {
|
||||||
return &TowerRepository{db: db}
|
return &TowerRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -17,7 +18,7 @@ func setupTowerRepo(t *testing.T) (*TowerRepository, *sqlx.DB, uint32, uint32) {
|
|||||||
if _, err := db.Exec("INSERT INTO guild_characters (guild_id, character_id) VALUES ($1, $2)", guildID, charID); err != nil {
|
if _, err := db.Exec("INSERT INTO guild_characters (guild_id, character_id) VALUES ($1, $2)", guildID, charID); err != nil {
|
||||||
t.Fatalf("Failed to add char to guild: %v", err)
|
t.Fatalf("Failed to add char to guild: %v", err)
|
||||||
}
|
}
|
||||||
repo := NewTowerRepository(db)
|
repo := NewTowerRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, charID, guildID
|
return repo, db, charID, guildID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserRepository centralizes all database access for the users table.
|
// UserRepository centralizes all database access for the users table.
|
||||||
type UserRepository struct {
|
type UserRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserRepository creates a new UserRepository.
|
// NewUserRepository creates a new UserRepository.
|
||||||
func NewUserRepository(db *sqlx.DB) *UserRepository {
|
func NewUserRepository(db *dbutil.DB) *UserRepository {
|
||||||
return &UserRepository{db: db}
|
return &UserRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package channelserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ func setupUserRepo(t *testing.T) (*UserRepository, *sqlx.DB, uint32) {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
db := SetupTestDB(t)
|
db := SetupTestDB(t)
|
||||||
userID := CreateTestUser(t, db, "user_repo_test")
|
userID := CreateTestUser(t, db, "user_repo_test")
|
||||||
repo := NewUserRepository(db)
|
repo := NewUserRepository(dbutil.Wrap(db))
|
||||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||||
return repo, db, userID
|
return repo, db, userID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"erupe-ce/common/mhfitem"
|
"erupe-ce/common/mhfitem"
|
||||||
cfg "erupe-ce/config"
|
cfg "erupe-ce/config"
|
||||||
"erupe-ce/network/clientctx"
|
"erupe-ce/network/clientctx"
|
||||||
@@ -597,18 +598,19 @@ func createTestServerWithDB(t *testing.T, db *sqlx.DB) *Server {
|
|||||||
server.logger = logger
|
server.logger = logger
|
||||||
|
|
||||||
// Initialize repositories
|
// Initialize repositories
|
||||||
server.charRepo = NewCharacterRepository(db)
|
wdb := dbutil.Wrap(db)
|
||||||
server.guildRepo = NewGuildRepository(db)
|
server.charRepo = NewCharacterRepository(wdb)
|
||||||
server.userRepo = NewUserRepository(db)
|
server.guildRepo = NewGuildRepository(wdb)
|
||||||
server.gachaRepo = NewGachaRepository(db)
|
server.userRepo = NewUserRepository(wdb)
|
||||||
server.houseRepo = NewHouseRepository(db)
|
server.gachaRepo = NewGachaRepository(wdb)
|
||||||
server.festaRepo = NewFestaRepository(db)
|
server.houseRepo = NewHouseRepository(wdb)
|
||||||
server.towerRepo = NewTowerRepository(db)
|
server.festaRepo = NewFestaRepository(wdb)
|
||||||
server.rengokuRepo = NewRengokuRepository(db)
|
server.towerRepo = NewTowerRepository(wdb)
|
||||||
server.mailRepo = NewMailRepository(db)
|
server.rengokuRepo = NewRengokuRepository(wdb)
|
||||||
server.stampRepo = NewStampRepository(db)
|
server.mailRepo = NewMailRepository(wdb)
|
||||||
server.distRepo = NewDistributionRepository(db)
|
server.stampRepo = NewStampRepository(wdb)
|
||||||
server.sessionRepo = NewSessionRepository(db)
|
server.distRepo = NewDistributionRepository(wdb)
|
||||||
|
server.sessionRepo = NewSessionRepository(wdb)
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"erupe-ce/common/byteframe"
|
"erupe-ce/common/byteframe"
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
cfg "erupe-ce/config"
|
cfg "erupe-ce/config"
|
||||||
"erupe-ce/network"
|
"erupe-ce/network"
|
||||||
"erupe-ce/network/binpacket"
|
"erupe-ce/network/binpacket"
|
||||||
@@ -142,27 +143,28 @@ func NewServer(config *Config) *Server {
|
|||||||
handlerTable: buildHandlerTable(),
|
handlerTable: buildHandlerTable(),
|
||||||
}
|
}
|
||||||
|
|
||||||
s.charRepo = NewCharacterRepository(config.DB)
|
wdb := dbutil.Wrap(config.DB)
|
||||||
s.guildRepo = NewGuildRepository(config.DB)
|
s.charRepo = NewCharacterRepository(wdb)
|
||||||
s.userRepo = NewUserRepository(config.DB)
|
s.guildRepo = NewGuildRepository(wdb)
|
||||||
s.gachaRepo = NewGachaRepository(config.DB)
|
s.userRepo = NewUserRepository(wdb)
|
||||||
s.houseRepo = NewHouseRepository(config.DB)
|
s.gachaRepo = NewGachaRepository(wdb)
|
||||||
s.festaRepo = NewFestaRepository(config.DB)
|
s.houseRepo = NewHouseRepository(wdb)
|
||||||
s.towerRepo = NewTowerRepository(config.DB)
|
s.festaRepo = NewFestaRepository(wdb)
|
||||||
s.rengokuRepo = NewRengokuRepository(config.DB)
|
s.towerRepo = NewTowerRepository(wdb)
|
||||||
s.mailRepo = NewMailRepository(config.DB)
|
s.rengokuRepo = NewRengokuRepository(wdb)
|
||||||
s.stampRepo = NewStampRepository(config.DB)
|
s.mailRepo = NewMailRepository(wdb)
|
||||||
s.distRepo = NewDistributionRepository(config.DB)
|
s.stampRepo = NewStampRepository(wdb)
|
||||||
s.sessionRepo = NewSessionRepository(config.DB)
|
s.distRepo = NewDistributionRepository(wdb)
|
||||||
s.eventRepo = NewEventRepository(config.DB)
|
s.sessionRepo = NewSessionRepository(wdb)
|
||||||
s.achievementRepo = NewAchievementRepository(config.DB)
|
s.eventRepo = NewEventRepository(wdb)
|
||||||
s.shopRepo = NewShopRepository(config.DB)
|
s.achievementRepo = NewAchievementRepository(wdb)
|
||||||
s.cafeRepo = NewCafeRepository(config.DB)
|
s.shopRepo = NewShopRepository(wdb)
|
||||||
s.goocooRepo = NewGoocooRepository(config.DB)
|
s.cafeRepo = NewCafeRepository(wdb)
|
||||||
s.divaRepo = NewDivaRepository(config.DB)
|
s.goocooRepo = NewGoocooRepository(wdb)
|
||||||
s.miscRepo = NewMiscRepository(config.DB)
|
s.divaRepo = NewDivaRepository(wdb)
|
||||||
s.scenarioRepo = NewScenarioRepository(config.DB)
|
s.miscRepo = NewMiscRepository(wdb)
|
||||||
s.mercenaryRepo = NewMercenaryRepository(config.DB)
|
s.scenarioRepo = NewScenarioRepository(wdb)
|
||||||
|
s.mercenaryRepo = NewMercenaryRepository(wdb)
|
||||||
|
|
||||||
s.mailService = NewMailService(s.mailRepo, s.guildRepo, s.logger)
|
s.mailService = NewMailService(s.mailRepo, s.guildRepo, s.logger)
|
||||||
s.guildService = NewGuildService(s.guildRepo, s.mailService, s.charRepo, s.logger)
|
s.guildService = NewGuildService(s.guildRepo, s.mailService, s.charRepo, s.logger)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
"erupe-ce/server/channelserver/compression/nullcomp"
|
"erupe-ce/server/channelserver/compression/nullcomp"
|
||||||
"erupe-ce/server/migrations"
|
"erupe-ce/server/migrations"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -330,25 +331,26 @@ func CreateTestGachaItem(t *testing.T, db *sqlx.DB, entryID uint32, itemType uin
|
|||||||
// Use this in integration tests instead of setting s.server.db directly.
|
// Use this in integration tests instead of setting s.server.db directly.
|
||||||
func SetTestDB(s *Server, db *sqlx.DB) {
|
func SetTestDB(s *Server, db *sqlx.DB) {
|
||||||
s.db = db
|
s.db = db
|
||||||
s.charRepo = NewCharacterRepository(db)
|
wdb := dbutil.Wrap(db)
|
||||||
s.guildRepo = NewGuildRepository(db)
|
s.charRepo = NewCharacterRepository(wdb)
|
||||||
s.userRepo = NewUserRepository(db)
|
s.guildRepo = NewGuildRepository(wdb)
|
||||||
s.gachaRepo = NewGachaRepository(db)
|
s.userRepo = NewUserRepository(wdb)
|
||||||
s.houseRepo = NewHouseRepository(db)
|
s.gachaRepo = NewGachaRepository(wdb)
|
||||||
s.festaRepo = NewFestaRepository(db)
|
s.houseRepo = NewHouseRepository(wdb)
|
||||||
s.towerRepo = NewTowerRepository(db)
|
s.festaRepo = NewFestaRepository(wdb)
|
||||||
s.rengokuRepo = NewRengokuRepository(db)
|
s.towerRepo = NewTowerRepository(wdb)
|
||||||
s.mailRepo = NewMailRepository(db)
|
s.rengokuRepo = NewRengokuRepository(wdb)
|
||||||
s.stampRepo = NewStampRepository(db)
|
s.mailRepo = NewMailRepository(wdb)
|
||||||
s.distRepo = NewDistributionRepository(db)
|
s.stampRepo = NewStampRepository(wdb)
|
||||||
s.sessionRepo = NewSessionRepository(db)
|
s.distRepo = NewDistributionRepository(wdb)
|
||||||
s.eventRepo = NewEventRepository(db)
|
s.sessionRepo = NewSessionRepository(wdb)
|
||||||
s.achievementRepo = NewAchievementRepository(db)
|
s.eventRepo = NewEventRepository(wdb)
|
||||||
s.shopRepo = NewShopRepository(db)
|
s.achievementRepo = NewAchievementRepository(wdb)
|
||||||
s.cafeRepo = NewCafeRepository(db)
|
s.shopRepo = NewShopRepository(wdb)
|
||||||
s.goocooRepo = NewGoocooRepository(db)
|
s.cafeRepo = NewCafeRepository(wdb)
|
||||||
s.divaRepo = NewDivaRepository(db)
|
s.goocooRepo = NewGoocooRepository(wdb)
|
||||||
s.miscRepo = NewMiscRepository(db)
|
s.divaRepo = NewDivaRepository(wdb)
|
||||||
s.scenarioRepo = NewScenarioRepository(db)
|
s.miscRepo = NewMiscRepository(wdb)
|
||||||
s.mercenaryRepo = NewMercenaryRepository(db)
|
s.scenarioRepo = NewScenarioRepository(wdb)
|
||||||
|
s.mercenaryRepo = NewMercenaryRepository(wdb)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,18 +12,18 @@ import (
|
|||||||
|
|
||||||
// mockSession implements the Session interface for testing.
|
// mockSession implements the Session interface for testing.
|
||||||
type mockSession struct {
|
type mockSession struct {
|
||||||
openErr error
|
openErr error
|
||||||
channelResult *discordgo.Channel
|
channelResult *discordgo.Channel
|
||||||
channelErr error
|
channelErr error
|
||||||
userResults map[string]*discordgo.User
|
userResults map[string]*discordgo.User
|
||||||
userErr error
|
userErr error
|
||||||
messageSentTo string
|
messageSentTo string
|
||||||
messageSentContent string
|
messageSentContent string
|
||||||
messageErr error
|
messageErr error
|
||||||
addHandlerCalls int
|
addHandlerCalls int
|
||||||
bulkOverwriteAppID string
|
bulkOverwriteAppID string
|
||||||
bulkOverwriteCommands []*discordgo.ApplicationCommand
|
bulkOverwriteCommands []*discordgo.ApplicationCommand
|
||||||
bulkOverwriteErr error
|
bulkOverwriteErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockSession) Open() error {
|
func (m *mockSession) Open() error {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@@ -15,6 +17,9 @@ import (
|
|||||||
//go:embed sql/*.sql
|
//go:embed sql/*.sql
|
||||||
var migrationFS embed.FS
|
var migrationFS embed.FS
|
||||||
|
|
||||||
|
//go:embed sqlite/*.sql
|
||||||
|
var sqliteMigrationFS embed.FS
|
||||||
|
|
||||||
//go:embed seed/*.sql
|
//go:embed seed/*.sql
|
||||||
var seedFS embed.FS
|
var seedFS embed.FS
|
||||||
|
|
||||||
@@ -22,15 +27,17 @@ var seedFS embed.FS
|
|||||||
// (auto-marks baseline as applied), then runs all pending migrations in order.
|
// (auto-marks baseline as applied), then runs all pending migrations in order.
|
||||||
// Each migration runs in its own transaction.
|
// Each migration runs in its own transaction.
|
||||||
func Migrate(db *sqlx.DB, logger *zap.Logger) (int, error) {
|
func Migrate(db *sqlx.DB, logger *zap.Logger) (int, error) {
|
||||||
if err := ensureVersionTable(db); err != nil {
|
sqlite := dbutil.IsSQLite(db)
|
||||||
|
|
||||||
|
if err := ensureVersionTable(db, sqlite); err != nil {
|
||||||
return 0, fmt.Errorf("creating schema_version table: %w", err)
|
return 0, fmt.Errorf("creating schema_version table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := detectExistingDB(db, logger); err != nil {
|
if err := detectExistingDB(db, logger, sqlite); err != nil {
|
||||||
return 0, fmt.Errorf("detecting existing database: %w", err)
|
return 0, fmt.Errorf("detecting existing database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
migrations, err := readMigrations()
|
migrations, err := readMigrations(sqlite)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("reading migration files: %w", err)
|
return 0, fmt.Errorf("reading migration files: %w", err)
|
||||||
}
|
}
|
||||||
@@ -46,7 +53,7 @@ func Migrate(db *sqlx.DB, logger *zap.Logger) (int, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
logger.Info(fmt.Sprintf("Applying migration %04d: %s", m.version, m.filename))
|
logger.Info(fmt.Sprintf("Applying migration %04d: %s", m.version, m.filename))
|
||||||
if err := applyMigration(db, m); err != nil {
|
if err := applyMigration(db, m, sqlite); err != nil {
|
||||||
return count, fmt.Errorf("applying %s: %w", m.filename, err)
|
return count, fmt.Errorf("applying %s: %w", m.filename, err)
|
||||||
}
|
}
|
||||||
count++
|
count++
|
||||||
@@ -58,6 +65,7 @@ func Migrate(db *sqlx.DB, logger *zap.Logger) (int, error) {
|
|||||||
// ApplySeedData runs all seed/*.sql files. Not tracked in schema_version.
|
// ApplySeedData runs all seed/*.sql files. Not tracked in schema_version.
|
||||||
// Safe to run multiple times if seed files use ON CONFLICT DO NOTHING.
|
// Safe to run multiple times if seed files use ON CONFLICT DO NOTHING.
|
||||||
func ApplySeedData(db *sqlx.DB, logger *zap.Logger) (int, error) {
|
func ApplySeedData(db *sqlx.DB, logger *zap.Logger) (int, error) {
|
||||||
|
sqlite := dbutil.IsSQLite(db)
|
||||||
files, err := fs.ReadDir(seedFS, "seed")
|
files, err := fs.ReadDir(seedFS, "seed")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("reading seed directory: %w", err)
|
return 0, fmt.Errorf("reading seed directory: %w", err)
|
||||||
@@ -78,7 +86,11 @@ func ApplySeedData(db *sqlx.DB, logger *zap.Logger) (int, error) {
|
|||||||
return count, fmt.Errorf("reading seed file %s: %w", name, err)
|
return count, fmt.Errorf("reading seed file %s: %w", name, err)
|
||||||
}
|
}
|
||||||
logger.Info(fmt.Sprintf("Applying seed data: %s", name))
|
logger.Info(fmt.Sprintf("Applying seed data: %s", name))
|
||||||
if _, err := db.Exec(string(data)); err != nil {
|
sql := string(data)
|
||||||
|
if sqlite {
|
||||||
|
sql = dbutil.Adapt(db, sql)
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(sql); err != nil {
|
||||||
return count, fmt.Errorf("executing seed file %s: %w", name, err)
|
return count, fmt.Errorf("executing seed file %s: %w", name, err)
|
||||||
}
|
}
|
||||||
count++
|
count++
|
||||||
@@ -88,20 +100,30 @@ func ApplySeedData(db *sqlx.DB, logger *zap.Logger) (int, error) {
|
|||||||
|
|
||||||
// Version returns the highest applied migration number, or 0 if none.
|
// Version returns the highest applied migration number, or 0 if none.
|
||||||
func Version(db *sqlx.DB) (int, error) {
|
func Version(db *sqlx.DB) (int, error) {
|
||||||
|
sqlite := dbutil.IsSQLite(db)
|
||||||
|
|
||||||
var exists bool
|
var exists bool
|
||||||
err := db.QueryRow(`SELECT EXISTS(
|
if sqlite {
|
||||||
SELECT 1 FROM information_schema.tables
|
err := db.QueryRow(`SELECT COUNT(*) > 0 FROM sqlite_master
|
||||||
WHERE table_schema = 'public' AND table_name = 'schema_version'
|
WHERE type='table' AND name='schema_version'`).Scan(&exists)
|
||||||
)`).Scan(&exists)
|
if err != nil {
|
||||||
if err != nil {
|
return 0, err
|
||||||
return 0, err
|
}
|
||||||
|
} else {
|
||||||
|
err := db.QueryRow(`SELECT EXISTS(
|
||||||
|
SELECT 1 FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public' AND table_name = 'schema_version'
|
||||||
|
)`).Scan(&exists)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !exists {
|
if !exists {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var version int
|
var version int
|
||||||
err = db.QueryRow("SELECT COALESCE(MAX(version), 0) FROM schema_version").Scan(&version)
|
err := db.QueryRow("SELECT COALESCE(MAX(version), 0) FROM schema_version").Scan(&version)
|
||||||
return version, err
|
return version, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,18 +133,26 @@ type migration struct {
|
|||||||
sql string
|
sql string
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureVersionTable(db *sqlx.DB) error {
|
func ensureVersionTable(db *sqlx.DB, sqlite bool) error {
|
||||||
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS schema_version (
|
q := `CREATE TABLE IF NOT EXISTS schema_version (
|
||||||
version INTEGER PRIMARY KEY,
|
version INTEGER PRIMARY KEY,
|
||||||
filename TEXT NOT NULL,
|
filename TEXT NOT NULL,
|
||||||
applied_at TIMESTAMPTZ DEFAULT now()
|
applied_at TIMESTAMPTZ DEFAULT now()
|
||||||
)`)
|
)`
|
||||||
|
if sqlite {
|
||||||
|
q = `CREATE TABLE IF NOT EXISTS schema_version (
|
||||||
|
version INTEGER PRIMARY KEY,
|
||||||
|
filename TEXT NOT NULL,
|
||||||
|
applied_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)`
|
||||||
|
}
|
||||||
|
_, err := db.Exec(q)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// detectExistingDB checks if the database has tables but no schema_version rows.
|
// detectExistingDB checks if the database has tables but no schema_version rows.
|
||||||
// If so, it marks the baseline migration (version 1) as already applied.
|
// If so, it marks the baseline migration (version 1) as already applied.
|
||||||
func detectExistingDB(db *sqlx.DB, logger *zap.Logger) error {
|
func detectExistingDB(db *sqlx.DB, logger *zap.Logger, sqlite bool) error {
|
||||||
var count int
|
var count int
|
||||||
if err := db.QueryRow("SELECT COUNT(*) FROM schema_version").Scan(&count); err != nil {
|
if err := db.QueryRow("SELECT COUNT(*) FROM schema_version").Scan(&count); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -133,10 +163,18 @@ func detectExistingDB(db *sqlx.DB, logger *zap.Logger) error {
|
|||||||
|
|
||||||
// Check if the database has any user tables (beyond schema_version itself)
|
// Check if the database has any user tables (beyond schema_version itself)
|
||||||
var tableCount int
|
var tableCount int
|
||||||
err := db.QueryRow(`SELECT COUNT(*) FROM information_schema.tables
|
if sqlite {
|
||||||
WHERE table_schema = 'public' AND table_name != 'schema_version'`).Scan(&tableCount)
|
err := db.QueryRow(`SELECT COUNT(*) FROM sqlite_master
|
||||||
if err != nil {
|
WHERE type='table' AND name != 'schema_version'`).Scan(&tableCount)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := db.QueryRow(`SELECT COUNT(*) FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public' AND table_name != 'schema_version'`).Scan(&tableCount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if tableCount == 0 {
|
if tableCount == 0 {
|
||||||
return nil // Fresh database
|
return nil // Fresh database
|
||||||
@@ -144,12 +182,22 @@ func detectExistingDB(db *sqlx.DB, logger *zap.Logger) error {
|
|||||||
|
|
||||||
// Existing database without migration tracking — mark baseline as applied
|
// Existing database without migration tracking — mark baseline as applied
|
||||||
logger.Info("Detected existing database without schema_version tracking, marking baseline as applied")
|
logger.Info("Detected existing database without schema_version tracking, marking baseline as applied")
|
||||||
_, err = db.Exec("INSERT INTO schema_version (version, filename) VALUES (1, '0001_init.sql')")
|
_, err := db.Exec("INSERT INTO schema_version (version, filename) VALUES (1, '0001_init.sql')")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func readMigrations() ([]migration, error) {
|
func readMigrations(sqlite bool) ([]migration, error) {
|
||||||
files, err := fs.ReadDir(migrationFS, "sql")
|
var embedFS embed.FS
|
||||||
|
var dir string
|
||||||
|
if sqlite {
|
||||||
|
embedFS = sqliteMigrationFS
|
||||||
|
dir = "sqlite"
|
||||||
|
} else {
|
||||||
|
embedFS = migrationFS
|
||||||
|
dir = "sql"
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := fs.ReadDir(embedFS, dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -163,7 +211,7 @@ func readMigrations() ([]migration, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parsing version from %s: %w", f.Name(), err)
|
return nil, fmt.Errorf("parsing version from %s: %w", f.Name(), err)
|
||||||
}
|
}
|
||||||
data, err := migrationFS.ReadFile("sql/" + f.Name())
|
data, err := embedFS.ReadFile(dir + "/" + f.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -206,7 +254,7 @@ func appliedVersions(db *sqlx.DB) (map[int]bool, error) {
|
|||||||
return applied, rows.Err()
|
return applied, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyMigration(db *sqlx.DB, m migration) error {
|
func applyMigration(db *sqlx.DB, m migration, sqlite bool) error {
|
||||||
tx, err := db.Begin()
|
tx, err := db.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -217,10 +265,11 @@ func applyMigration(db *sqlx.DB, m migration) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := tx.Exec(
|
insertQ := "INSERT INTO schema_version (version, filename) VALUES ($1, $2)"
|
||||||
"INSERT INTO schema_version (version, filename) VALUES ($1, $2)",
|
if sqlite {
|
||||||
m.version, m.filename,
|
insertQ = "INSERT INTO schema_version (version, filename) VALUES (?, ?)"
|
||||||
); err != nil {
|
}
|
||||||
|
if _, err := tx.Exec(insertQ, m.version, m.filename); err != nil {
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testDB(t *testing.T) *sqlx.DB {
|
func testDB(t *testing.T) *sqlx.DB {
|
||||||
@@ -194,7 +195,7 @@ func TestParseVersion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadMigrations(t *testing.T) {
|
func TestReadMigrations(t *testing.T) {
|
||||||
migrations, err := readMigrations()
|
migrations, err := readMigrations(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("readMigrations failed: %v", err)
|
t.Fatalf("readMigrations failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -210,7 +211,7 @@ func TestReadMigrations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadMigrations_Sorted(t *testing.T) {
|
func TestReadMigrations_Sorted(t *testing.T) {
|
||||||
migrations, err := readMigrations()
|
migrations, err := readMigrations(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("readMigrations failed: %v", err)
|
t.Fatalf("readMigrations failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -223,7 +224,7 @@ func TestReadMigrations_Sorted(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadMigrations_AllHaveSQL(t *testing.T) {
|
func TestReadMigrations_AllHaveSQL(t *testing.T) {
|
||||||
migrations, err := readMigrations()
|
migrations, err := readMigrations(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("readMigrations failed: %v", err)
|
t.Fatalf("readMigrations failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -235,7 +236,7 @@ func TestReadMigrations_AllHaveSQL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadMigrations_BaselineIsLargest(t *testing.T) {
|
func TestReadMigrations_BaselineIsLargest(t *testing.T) {
|
||||||
migrations, err := readMigrations()
|
migrations, err := readMigrations(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("readMigrations failed: %v", err)
|
t.Fatalf("readMigrations failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -252,6 +253,61 @@ func TestReadMigrations_BaselineIsLargest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReadMigrations_SQLite(t *testing.T) {
|
||||||
|
migrations, err := readMigrations(true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("readMigrations(sqlite) failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(migrations) != 5 {
|
||||||
|
t.Fatalf("expected 5 SQLite migrations, got %d", len(migrations))
|
||||||
|
}
|
||||||
|
if migrations[0].filename != "0001_init.sql" {
|
||||||
|
t.Errorf("first SQLite migration = %q, want 0001_init.sql", migrations[0].filename)
|
||||||
|
}
|
||||||
|
for _, m := range migrations {
|
||||||
|
if m.sql == "" {
|
||||||
|
t.Errorf("SQLite migration %s has empty SQL", m.filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSQLiteMigrateInMemory(t *testing.T) {
|
||||||
|
db, err := sqlx.Open("sqlite", ":memory:?_pragma=foreign_keys(1)")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to open in-memory SQLite: %v", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = db.Close() }()
|
||||||
|
|
||||||
|
logger, _ := zap.NewDevelopment()
|
||||||
|
applied, err := Migrate(db, logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Migrate to SQLite failed: %v", err)
|
||||||
|
}
|
||||||
|
if applied != 5 {
|
||||||
|
t.Errorf("expected 5 migrations applied, got %d", applied)
|
||||||
|
}
|
||||||
|
|
||||||
|
ver, err := Version(db)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Version failed: %v", err)
|
||||||
|
}
|
||||||
|
if ver != 5 {
|
||||||
|
t.Errorf("expected version 5, got %d", ver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify a few key tables exist
|
||||||
|
for _, table := range []string{"users", "characters", "guilds", "guild_characters", "sign_sessions"} {
|
||||||
|
var count int
|
||||||
|
err := db.QueryRow("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?", table).Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to check table %s: %v", table, err)
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
t.Errorf("Table %s not found in SQLite schema", table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseVersion_Comprehensive(t *testing.T) {
|
func TestParseVersion_Comprehensive(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
filename string
|
filename string
|
||||||
|
|||||||
855
server/migrations/sqlite/0001_init.sql
Normal file
855
server/migrations/sqlite/0001_init.sql
Normal file
@@ -0,0 +1,855 @@
|
|||||||
|
-- Erupe consolidated database schema (SQLite)
|
||||||
|
-- Translated from PostgreSQL 0001_init.sql
|
||||||
|
-- Compatible with modernc.org/sqlite
|
||||||
|
--
|
||||||
|
-- Includes: init.sql (v9.1.0) + 9.2-update.sql + all 33 patch schemas
|
||||||
|
|
||||||
|
PRAGMA journal_mode=WAL;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: achievements; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE achievements (
|
||||||
|
id integer NOT NULL,
|
||||||
|
ach0 integer DEFAULT 0,
|
||||||
|
ach1 integer DEFAULT 0,
|
||||||
|
ach2 integer DEFAULT 0,
|
||||||
|
ach3 integer DEFAULT 0,
|
||||||
|
ach4 integer DEFAULT 0,
|
||||||
|
ach5 integer DEFAULT 0,
|
||||||
|
ach6 integer DEFAULT 0,
|
||||||
|
ach7 integer DEFAULT 0,
|
||||||
|
ach8 integer DEFAULT 0,
|
||||||
|
ach9 integer DEFAULT 0,
|
||||||
|
ach10 integer DEFAULT 0,
|
||||||
|
ach11 integer DEFAULT 0,
|
||||||
|
ach12 integer DEFAULT 0,
|
||||||
|
ach13 integer DEFAULT 0,
|
||||||
|
ach14 integer DEFAULT 0,
|
||||||
|
ach15 integer DEFAULT 0,
|
||||||
|
ach16 integer DEFAULT 0,
|
||||||
|
ach17 integer DEFAULT 0,
|
||||||
|
ach18 integer DEFAULT 0,
|
||||||
|
ach19 integer DEFAULT 0,
|
||||||
|
ach20 integer DEFAULT 0,
|
||||||
|
ach21 integer DEFAULT 0,
|
||||||
|
ach22 integer DEFAULT 0,
|
||||||
|
ach23 integer DEFAULT 0,
|
||||||
|
ach24 integer DEFAULT 0,
|
||||||
|
ach25 integer DEFAULT 0,
|
||||||
|
ach26 integer DEFAULT 0,
|
||||||
|
ach27 integer DEFAULT 0,
|
||||||
|
ach28 integer DEFAULT 0,
|
||||||
|
ach29 integer DEFAULT 0,
|
||||||
|
ach30 integer DEFAULT 0,
|
||||||
|
ach31 integer DEFAULT 0,
|
||||||
|
ach32 integer DEFAULT 0,
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: bans; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE bans (
|
||||||
|
user_id integer NOT NULL,
|
||||||
|
expires TEXT,
|
||||||
|
PRIMARY KEY (user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: cafe_accepted; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE cafe_accepted (
|
||||||
|
cafe_id integer NOT NULL,
|
||||||
|
character_id integer NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: cafebonus; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE cafebonus (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
time_req integer NOT NULL,
|
||||||
|
item_type integer NOT NULL,
|
||||||
|
item_id integer NOT NULL,
|
||||||
|
quantity integer NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: characters; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE characters (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
user_id bigint,
|
||||||
|
is_female boolean,
|
||||||
|
is_new_character boolean,
|
||||||
|
name TEXT,
|
||||||
|
unk_desc_string TEXT,
|
||||||
|
gr INTEGER,
|
||||||
|
hr INTEGER,
|
||||||
|
weapon_type INTEGER,
|
||||||
|
last_login integer,
|
||||||
|
savedata BLOB,
|
||||||
|
decomyset BLOB,
|
||||||
|
hunternavi BLOB,
|
||||||
|
otomoairou BLOB,
|
||||||
|
partner BLOB,
|
||||||
|
platebox BLOB,
|
||||||
|
platedata BLOB,
|
||||||
|
platemyset BLOB,
|
||||||
|
rengokudata BLOB,
|
||||||
|
savemercenary BLOB,
|
||||||
|
restrict_guild_scout boolean DEFAULT false NOT NULL,
|
||||||
|
gacha_items BLOB,
|
||||||
|
daily_time TEXT,
|
||||||
|
house_info BLOB,
|
||||||
|
login_boost BLOB,
|
||||||
|
skin_hist BLOB,
|
||||||
|
kouryou_point integer,
|
||||||
|
gcp integer,
|
||||||
|
guild_post_checked TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
time_played integer DEFAULT 0 NOT NULL,
|
||||||
|
weapon_id integer DEFAULT 0 NOT NULL,
|
||||||
|
scenariodata BLOB,
|
||||||
|
savefavoritequest BLOB,
|
||||||
|
friends text DEFAULT '' NOT NULL,
|
||||||
|
blocked text DEFAULT '' NOT NULL,
|
||||||
|
deleted boolean DEFAULT false NOT NULL,
|
||||||
|
cafe_time integer DEFAULT 0,
|
||||||
|
netcafe_points integer DEFAULT 0,
|
||||||
|
boost_time TEXT,
|
||||||
|
cafe_reset TEXT,
|
||||||
|
bonus_quests integer DEFAULT 0 NOT NULL,
|
||||||
|
daily_quests integer DEFAULT 0 NOT NULL,
|
||||||
|
promo_points integer DEFAULT 0 NOT NULL,
|
||||||
|
rasta_id integer,
|
||||||
|
pact_id integer,
|
||||||
|
stampcard integer DEFAULT 0 NOT NULL,
|
||||||
|
mezfes BLOB,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: distribution; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE distribution (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
character_id integer,
|
||||||
|
type integer NOT NULL,
|
||||||
|
deadline TEXT,
|
||||||
|
event_name text DEFAULT 'GM Gift!' NOT NULL,
|
||||||
|
description text DEFAULT '~C05You received a gift!' NOT NULL,
|
||||||
|
times_acceptable integer DEFAULT 1 NOT NULL,
|
||||||
|
min_hr integer,
|
||||||
|
max_hr integer,
|
||||||
|
min_sr integer,
|
||||||
|
max_sr integer,
|
||||||
|
min_gr integer,
|
||||||
|
max_gr integer,
|
||||||
|
rights integer,
|
||||||
|
selection boolean
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: distribution_items; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE distribution_items (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
distribution_id integer NOT NULL,
|
||||||
|
item_type integer NOT NULL,
|
||||||
|
item_id integer,
|
||||||
|
quantity integer
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: distributions_accepted; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE distributions_accepted (
|
||||||
|
distribution_id integer,
|
||||||
|
character_id integer
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: event_quests; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE event_quests (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
max_players integer,
|
||||||
|
quest_type integer NOT NULL,
|
||||||
|
quest_id integer NOT NULL,
|
||||||
|
mark integer,
|
||||||
|
flags integer,
|
||||||
|
start_time TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
active_days integer,
|
||||||
|
inactive_days integer
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: events; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE events (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
event_type TEXT NOT NULL CHECK (event_type IN ('festa','diva','vs','mezfes')),
|
||||||
|
start_time TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: feature_weapon; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE feature_weapon (
|
||||||
|
start_time TEXT NOT NULL,
|
||||||
|
featured integer NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: festa_prizes; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE festa_prizes (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
type TEXT NOT NULL CHECK (type IN ('personal','guild')),
|
||||||
|
tier integer NOT NULL,
|
||||||
|
souls_req integer NOT NULL,
|
||||||
|
item_id integer NOT NULL,
|
||||||
|
num_item integer NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: festa_prizes_accepted; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE festa_prizes_accepted (
|
||||||
|
prize_id integer NOT NULL,
|
||||||
|
character_id integer NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: festa_registrations; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE festa_registrations (
|
||||||
|
guild_id integer NOT NULL,
|
||||||
|
team TEXT NOT NULL CHECK (team IN ('none','red','blue'))
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: festa_submissions; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE festa_submissions (
|
||||||
|
character_id integer NOT NULL,
|
||||||
|
guild_id integer NOT NULL,
|
||||||
|
trial_type integer NOT NULL,
|
||||||
|
souls integer NOT NULL,
|
||||||
|
"timestamp" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: festa_trials; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE festa_trials (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
objective integer NOT NULL,
|
||||||
|
goal_id integer NOT NULL,
|
||||||
|
times_req integer NOT NULL,
|
||||||
|
locale_req integer DEFAULT 0 NOT NULL,
|
||||||
|
reward integer NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: fpoint_items; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE fpoint_items (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
item_type integer NOT NULL,
|
||||||
|
item_id integer NOT NULL,
|
||||||
|
quantity integer NOT NULL,
|
||||||
|
fpoints integer NOT NULL,
|
||||||
|
buyable boolean DEFAULT false NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: gacha_box; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE gacha_box (
|
||||||
|
gacha_id integer,
|
||||||
|
entry_id integer,
|
||||||
|
character_id integer
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: gacha_entries; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE gacha_entries (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
gacha_id integer,
|
||||||
|
entry_type integer,
|
||||||
|
item_type integer,
|
||||||
|
item_number integer,
|
||||||
|
item_quantity integer,
|
||||||
|
weight integer,
|
||||||
|
rarity integer,
|
||||||
|
rolls integer,
|
||||||
|
frontier_points integer,
|
||||||
|
daily_limit integer,
|
||||||
|
name text
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: gacha_items; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE gacha_items (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
entry_id integer,
|
||||||
|
item_type integer,
|
||||||
|
item_id integer,
|
||||||
|
quantity integer
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: gacha_shop; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE gacha_shop (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
min_gr integer,
|
||||||
|
min_hr integer,
|
||||||
|
name text,
|
||||||
|
url_banner text,
|
||||||
|
url_feature text,
|
||||||
|
url_thumbnail text,
|
||||||
|
wide boolean,
|
||||||
|
recommended boolean,
|
||||||
|
gacha_type integer,
|
||||||
|
hidden boolean
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: gacha_stepup; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE gacha_stepup (
|
||||||
|
gacha_id integer,
|
||||||
|
step integer,
|
||||||
|
character_id integer,
|
||||||
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: goocoo; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE goocoo (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
goocoo0 BLOB,
|
||||||
|
goocoo1 BLOB,
|
||||||
|
goocoo2 BLOB,
|
||||||
|
goocoo3 BLOB,
|
||||||
|
goocoo4 BLOB
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: guild_adventures; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE guild_adventures (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
guild_id integer NOT NULL,
|
||||||
|
destination integer NOT NULL,
|
||||||
|
charge integer DEFAULT 0 NOT NULL,
|
||||||
|
depart integer NOT NULL,
|
||||||
|
"return" integer NOT NULL,
|
||||||
|
collected_by text DEFAULT '' NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: guild_alliances; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE guild_alliances (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
parent_id integer NOT NULL,
|
||||||
|
sub1_id integer,
|
||||||
|
sub2_id integer
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: guild_applications; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE guild_applications (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
guild_id integer NOT NULL,
|
||||||
|
character_id integer NOT NULL,
|
||||||
|
actor_id integer NOT NULL,
|
||||||
|
application_type TEXT NOT NULL CHECK (application_type IN ('applied','invited')),
|
||||||
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
UNIQUE (guild_id, character_id),
|
||||||
|
FOREIGN KEY (actor_id) REFERENCES characters(id),
|
||||||
|
FOREIGN KEY (character_id) REFERENCES characters(id),
|
||||||
|
FOREIGN KEY (guild_id) REFERENCES guilds(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: guild_characters; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE guild_characters (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
guild_id bigint,
|
||||||
|
character_id bigint,
|
||||||
|
joined_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
avoid_leadership boolean DEFAULT false NOT NULL,
|
||||||
|
order_index integer DEFAULT 1 NOT NULL,
|
||||||
|
recruiter boolean DEFAULT false NOT NULL,
|
||||||
|
rp_today integer DEFAULT 0,
|
||||||
|
rp_yesterday integer DEFAULT 0,
|
||||||
|
tower_mission_1 integer,
|
||||||
|
tower_mission_2 integer,
|
||||||
|
tower_mission_3 integer,
|
||||||
|
box_claimed TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
treasure_hunt integer,
|
||||||
|
trial_vote integer,
|
||||||
|
FOREIGN KEY (character_id) REFERENCES characters(id),
|
||||||
|
FOREIGN KEY (guild_id) REFERENCES guilds(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: guild_hunts; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE guild_hunts (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
guild_id integer NOT NULL,
|
||||||
|
host_id integer NOT NULL,
|
||||||
|
destination integer NOT NULL,
|
||||||
|
level integer NOT NULL,
|
||||||
|
acquired boolean DEFAULT false NOT NULL,
|
||||||
|
collected boolean DEFAULT false NOT NULL,
|
||||||
|
hunt_data BLOB NOT NULL,
|
||||||
|
cats_used text NOT NULL,
|
||||||
|
start TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: guild_hunts_claimed; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE guild_hunts_claimed (
|
||||||
|
hunt_id integer NOT NULL,
|
||||||
|
character_id integer NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: guild_meals; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE guild_meals (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
guild_id integer NOT NULL,
|
||||||
|
meal_id integer NOT NULL,
|
||||||
|
level integer NOT NULL,
|
||||||
|
created_at TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: guild_posts; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE guild_posts (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
guild_id integer NOT NULL,
|
||||||
|
author_id integer NOT NULL,
|
||||||
|
post_type integer NOT NULL,
|
||||||
|
stamp_id integer NOT NULL,
|
||||||
|
title text NOT NULL,
|
||||||
|
body text NOT NULL,
|
||||||
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
liked_by text DEFAULT '' NOT NULL,
|
||||||
|
deleted boolean DEFAULT false NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: guilds; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE guilds (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT,
|
||||||
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
leader_id integer NOT NULL,
|
||||||
|
main_motto integer DEFAULT 0,
|
||||||
|
rank_rp integer DEFAULT 0 NOT NULL,
|
||||||
|
comment TEXT DEFAULT '' NOT NULL,
|
||||||
|
icon BLOB,
|
||||||
|
sub_motto integer DEFAULT 0,
|
||||||
|
item_box BLOB,
|
||||||
|
event_rp integer DEFAULT 0 NOT NULL,
|
||||||
|
pugi_name_1 TEXT DEFAULT '',
|
||||||
|
pugi_name_2 TEXT DEFAULT '',
|
||||||
|
pugi_name_3 TEXT DEFAULT '',
|
||||||
|
recruiting boolean DEFAULT true NOT NULL,
|
||||||
|
pugi_outfit_1 integer DEFAULT 0 NOT NULL,
|
||||||
|
pugi_outfit_2 integer DEFAULT 0 NOT NULL,
|
||||||
|
pugi_outfit_3 integer DEFAULT 0 NOT NULL,
|
||||||
|
pugi_outfits integer DEFAULT 0 NOT NULL,
|
||||||
|
tower_mission_page integer DEFAULT 1,
|
||||||
|
tower_rp integer DEFAULT 0,
|
||||||
|
room_rp integer DEFAULT 0,
|
||||||
|
room_expiry TEXT,
|
||||||
|
weekly_bonus_users integer DEFAULT 0 NOT NULL,
|
||||||
|
rp_reset_at TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: kill_logs; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE kill_logs (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
character_id integer NOT NULL,
|
||||||
|
monster integer NOT NULL,
|
||||||
|
quantity integer NOT NULL,
|
||||||
|
"timestamp" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: login_boost; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE login_boost (
|
||||||
|
char_id integer,
|
||||||
|
week_req integer,
|
||||||
|
expiration TEXT,
|
||||||
|
reset TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: mail; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE mail (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
sender_id integer NOT NULL,
|
||||||
|
recipient_id integer NOT NULL,
|
||||||
|
subject TEXT DEFAULT '' NOT NULL,
|
||||||
|
body TEXT DEFAULT '' NOT NULL,
|
||||||
|
read boolean DEFAULT false NOT NULL,
|
||||||
|
attached_item_received boolean DEFAULT false NOT NULL,
|
||||||
|
attached_item integer,
|
||||||
|
attached_item_amount integer DEFAULT 1 NOT NULL,
|
||||||
|
is_guild_invite boolean DEFAULT false NOT NULL,
|
||||||
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
deleted boolean DEFAULT false NOT NULL,
|
||||||
|
locked boolean DEFAULT false NOT NULL,
|
||||||
|
is_sys_message boolean DEFAULT false NOT NULL,
|
||||||
|
FOREIGN KEY (recipient_id) REFERENCES characters(id),
|
||||||
|
FOREIGN KEY (sender_id) REFERENCES characters(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: rengoku_score; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE rengoku_score (
|
||||||
|
character_id integer NOT NULL,
|
||||||
|
max_stages_mp integer,
|
||||||
|
max_points_mp integer,
|
||||||
|
max_stages_sp integer,
|
||||||
|
max_points_sp integer,
|
||||||
|
PRIMARY KEY (character_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: scenario_counter; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE scenario_counter (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
scenario_id numeric NOT NULL,
|
||||||
|
category_id numeric NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: servers; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE servers (
|
||||||
|
server_id integer NOT NULL,
|
||||||
|
current_players integer NOT NULL,
|
||||||
|
world_name text,
|
||||||
|
world_description text,
|
||||||
|
land integer
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: shop_items; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE shop_items (
|
||||||
|
shop_type integer,
|
||||||
|
shop_id integer,
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
item_id INTEGER,
|
||||||
|
cost integer,
|
||||||
|
quantity INTEGER,
|
||||||
|
min_hr INTEGER,
|
||||||
|
min_sr INTEGER,
|
||||||
|
min_gr INTEGER,
|
||||||
|
store_level INTEGER,
|
||||||
|
max_quantity INTEGER,
|
||||||
|
road_floors INTEGER,
|
||||||
|
road_fatalis INTEGER
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: shop_items_bought; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE shop_items_bought (
|
||||||
|
character_id integer,
|
||||||
|
shop_item_id integer,
|
||||||
|
bought integer
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS shop_items_bought_character_item_unique
|
||||||
|
ON shop_items_bought (character_id, shop_item_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: sign_sessions; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE sign_sessions (
|
||||||
|
user_id integer,
|
||||||
|
char_id integer,
|
||||||
|
token TEXT NOT NULL,
|
||||||
|
server_id integer,
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
psn_id text
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: stamps; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE stamps (
|
||||||
|
character_id integer NOT NULL,
|
||||||
|
hl_total integer DEFAULT 0,
|
||||||
|
hl_redeemed integer DEFAULT 0,
|
||||||
|
hl_checked TEXT,
|
||||||
|
ex_total integer DEFAULT 0,
|
||||||
|
ex_redeemed integer DEFAULT 0,
|
||||||
|
ex_checked TEXT,
|
||||||
|
monthly_claimed TEXT,
|
||||||
|
monthly_hl_claimed TEXT,
|
||||||
|
monthly_ex_claimed TEXT,
|
||||||
|
PRIMARY KEY (character_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: titles; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE titles (
|
||||||
|
id integer NOT NULL,
|
||||||
|
char_id integer NOT NULL,
|
||||||
|
unlocked_at TEXT,
|
||||||
|
updated_at TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: tower; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE tower (
|
||||||
|
char_id integer,
|
||||||
|
tr integer,
|
||||||
|
trp integer,
|
||||||
|
tsp integer,
|
||||||
|
block1 integer,
|
||||||
|
block2 integer,
|
||||||
|
skills text,
|
||||||
|
gems text
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: trend_weapons; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE trend_weapons (
|
||||||
|
weapon_id integer NOT NULL,
|
||||||
|
weapon_type integer NOT NULL,
|
||||||
|
count integer DEFAULT 0,
|
||||||
|
PRIMARY KEY (weapon_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_binary; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE user_binary (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
house_tier BLOB,
|
||||||
|
house_state integer,
|
||||||
|
house_password text,
|
||||||
|
house_data BLOB,
|
||||||
|
house_furniture BLOB,
|
||||||
|
bookshelf BLOB,
|
||||||
|
gallery BLOB,
|
||||||
|
tore BLOB,
|
||||||
|
garden BLOB,
|
||||||
|
mission BLOB
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: users; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE users (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
username text NOT NULL UNIQUE,
|
||||||
|
password text NOT NULL,
|
||||||
|
item_box BLOB,
|
||||||
|
rights integer DEFAULT 12 NOT NULL,
|
||||||
|
last_character integer DEFAULT 0,
|
||||||
|
last_login TEXT,
|
||||||
|
return_expires TEXT,
|
||||||
|
gacha_premium integer,
|
||||||
|
gacha_trial integer,
|
||||||
|
frontier_points integer,
|
||||||
|
psn_id text,
|
||||||
|
wiiu_key text,
|
||||||
|
discord_token text,
|
||||||
|
discord_id text,
|
||||||
|
op boolean,
|
||||||
|
timer boolean
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: warehouse; Type: TABLE
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE warehouse (
|
||||||
|
character_id integer NOT NULL,
|
||||||
|
item0 BLOB,
|
||||||
|
item1 BLOB,
|
||||||
|
item2 BLOB,
|
||||||
|
item3 BLOB,
|
||||||
|
item4 BLOB,
|
||||||
|
item5 BLOB,
|
||||||
|
item6 BLOB,
|
||||||
|
item7 BLOB,
|
||||||
|
item8 BLOB,
|
||||||
|
item9 BLOB,
|
||||||
|
item10 BLOB,
|
||||||
|
item0name text,
|
||||||
|
item1name text,
|
||||||
|
item2name text,
|
||||||
|
item3name text,
|
||||||
|
item4name text,
|
||||||
|
item5name text,
|
||||||
|
item6name text,
|
||||||
|
item7name text,
|
||||||
|
item8name text,
|
||||||
|
item9name text,
|
||||||
|
equip0 BLOB,
|
||||||
|
equip1 BLOB,
|
||||||
|
equip2 BLOB,
|
||||||
|
equip3 BLOB,
|
||||||
|
equip4 BLOB,
|
||||||
|
equip5 BLOB,
|
||||||
|
equip6 BLOB,
|
||||||
|
equip7 BLOB,
|
||||||
|
equip8 BLOB,
|
||||||
|
equip9 BLOB,
|
||||||
|
equip10 BLOB,
|
||||||
|
equip0name text,
|
||||||
|
equip1name text,
|
||||||
|
equip2name text,
|
||||||
|
equip3name text,
|
||||||
|
equip4name text,
|
||||||
|
equip5name text,
|
||||||
|
equip6name text,
|
||||||
|
equip7name text,
|
||||||
|
equip8name text,
|
||||||
|
equip9name text,
|
||||||
|
PRIMARY KEY (character_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX guild_application_type_index ON guild_applications (application_type);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX guild_character_unique_index ON guild_characters (character_id);
|
||||||
|
|
||||||
|
CREATE INDEX mail_recipient_deleted_created_id_index ON mail (recipient_id, deleted, created_at DESC, id DESC);
|
||||||
3
server/migrations/sqlite/0002_catch_up_patches.sql
Normal file
3
server/migrations/sqlite/0002_catch_up_patches.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
-- SQLite: no-op. The consolidated 0001_init.sql already includes all patch columns.
|
||||||
|
-- This file exists so the migration version numbering stays in sync with PostgreSQL.
|
||||||
|
SELECT 1;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- SQLite: no-op. The unique index is already in 0001_init.sql.
|
||||||
|
SELECT 1;
|
||||||
2
server/migrations/sqlite/0004_alliance_recruiting.sql
Normal file
2
server/migrations/sqlite/0004_alliance_recruiting.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- SQLite: no-op. The recruiting column is already in 0001_init.sql.
|
||||||
|
SELECT 1;
|
||||||
2
server/migrations/sqlite/0005_distribution_drop_data.sql
Normal file
2
server/migrations/sqlite/0005_distribution_drop_data.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- SQLite: no-op. The data column was never included in 0001_init.sql.
|
||||||
|
SELECT 1;
|
||||||
@@ -45,6 +45,15 @@ func (ws *wizardServer) handleClientModes(w http.ResponseWriter, _ *http.Request
|
|||||||
writeJSON(w, http.StatusOK, map[string]interface{}{"modes": clientModes()})
|
writeJSON(w, http.StatusOK, map[string]interface{}{"modes": clientModes()})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ws *wizardServer) handleCheckQuests(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
status := checkQuestFiles("")
|
||||||
|
writeJSON(w, http.StatusOK, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *wizardServer) handlePresets(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
writeJSON(w, http.StatusOK, map[string]interface{}{"presets": availablePresets()})
|
||||||
|
}
|
||||||
|
|
||||||
// testDBRequest is the JSON body for POST /api/setup/test-db.
|
// testDBRequest is the JSON body for POST /api/setup/test-db.
|
||||||
type testDBRequest struct {
|
type testDBRequest struct {
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ func Run(logger *zap.Logger, port int) error {
|
|||||||
r.HandleFunc("/api/setup/client-modes", ws.handleClientModes).Methods("GET")
|
r.HandleFunc("/api/setup/client-modes", ws.handleClientModes).Methods("GET")
|
||||||
r.HandleFunc("/api/setup/test-db", ws.handleTestDB).Methods("POST")
|
r.HandleFunc("/api/setup/test-db", ws.handleTestDB).Methods("POST")
|
||||||
r.HandleFunc("/api/setup/init-db", ws.handleInitDB).Methods("POST")
|
r.HandleFunc("/api/setup/init-db", ws.handleInitDB).Methods("POST")
|
||||||
|
r.HandleFunc("/api/setup/check-quests", ws.handleCheckQuests).Methods("GET")
|
||||||
|
r.HandleFunc("/api/setup/presets", ws.handlePresets).Methods("GET")
|
||||||
r.HandleFunc("/api/setup/finish", ws.handleFinish).Methods("POST")
|
r.HandleFunc("/api/setup/finish", ws.handleFinish).Methods("POST")
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
)
|
)
|
||||||
@@ -31,6 +32,7 @@ type FinishRequest struct {
|
|||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
ClientMode string `json:"clientMode"`
|
ClientMode string `json:"clientMode"`
|
||||||
AutoCreateAccount bool `json:"autoCreateAccount"`
|
AutoCreateAccount bool `json:"autoCreateAccount"`
|
||||||
|
Preset string `json:"preset"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildDefaultConfig produces a minimal config map with only user-provided values.
|
// buildDefaultConfig produces a minimal config map with only user-provided values.
|
||||||
@@ -40,7 +42,7 @@ func buildDefaultConfig(req FinishRequest) map[string]interface{} {
|
|||||||
if lang == "" {
|
if lang == "" {
|
||||||
lang = "jp"
|
lang = "jp"
|
||||||
}
|
}
|
||||||
return map[string]interface{}{
|
cfg := map[string]interface{}{
|
||||||
"Host": req.Host,
|
"Host": req.Host,
|
||||||
"Language": lang,
|
"Language": lang,
|
||||||
"ClientMode": req.ClientMode,
|
"ClientMode": req.ClientMode,
|
||||||
@@ -53,6 +55,156 @@ func buildDefaultConfig(req FinishRequest) map[string]interface{} {
|
|||||||
"Database": req.DBName,
|
"Database": req.DBName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply preset overrides. The "community" preset uses Viper defaults and
|
||||||
|
// adds nothing to the config file.
|
||||||
|
if overrides, ok := presetConfigs()[req.Preset]; ok {
|
||||||
|
for k, v := range overrides {
|
||||||
|
cfg[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// presetConfigs returns config overrides keyed by preset ID.
|
||||||
|
// The "community" preset is intentionally absent — it relies entirely on
|
||||||
|
// Viper defaults.
|
||||||
|
func presetConfigs() map[string]map[string]interface{} {
|
||||||
|
return map[string]map[string]interface{}{
|
||||||
|
"solo": {
|
||||||
|
"GameplayOptions": map[string]interface{}{
|
||||||
|
"HRPMultiplier": 3.0,
|
||||||
|
"SRPMultiplier": 3.0,
|
||||||
|
"GRPMultiplier": 3.0,
|
||||||
|
"GSRPMultiplier": 3.0,
|
||||||
|
"ZennyMultiplier": 2.0,
|
||||||
|
"GZennyMultiplier": 2.0,
|
||||||
|
"MaterialMultiplier": 2.0,
|
||||||
|
"GMaterialMultiplier": 2.0,
|
||||||
|
"ExtraCarves": 2,
|
||||||
|
"GExtraCarves": 2,
|
||||||
|
},
|
||||||
|
"Entrance": map[string]interface{}{
|
||||||
|
"Entries": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"Name": "Solo",
|
||||||
|
"Type": 1,
|
||||||
|
"Channels": []map[string]interface{}{
|
||||||
|
{"Port": 54001, "MaxPlayers": 100},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"small": {
|
||||||
|
"Entrance": map[string]interface{}{
|
||||||
|
"Entries": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"Name": "World 1",
|
||||||
|
"Type": 1,
|
||||||
|
"Channels": []map[string]interface{}{
|
||||||
|
{"Port": 54001, "MaxPlayers": 100},
|
||||||
|
{"Port": 54002, "MaxPlayers": 100},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rebalanced": {
|
||||||
|
"GameplayOptions": map[string]interface{}{
|
||||||
|
"HRPMultiplier": 2.0,
|
||||||
|
"SRPMultiplier": 2.0,
|
||||||
|
"GRPMultiplier": 2.0,
|
||||||
|
"GSRPMultiplier": 2.0,
|
||||||
|
"ExtraCarves": 1,
|
||||||
|
"GExtraCarves": 1,
|
||||||
|
},
|
||||||
|
"Entrance": map[string]interface{}{
|
||||||
|
"Entries": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"Name": "Normal",
|
||||||
|
"Type": 1,
|
||||||
|
"Channels": []map[string]interface{}{
|
||||||
|
{"Port": 54001, "MaxPlayers": 100},
|
||||||
|
{"Port": 54002, "MaxPlayers": 100},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Cities",
|
||||||
|
"Type": 2,
|
||||||
|
"Channels": []map[string]interface{}{
|
||||||
|
{"Port": 54003, "MaxPlayers": 100},
|
||||||
|
{"Port": 54004, "MaxPlayers": 100},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuestStatus holds the result of a quest files check.
|
||||||
|
type QuestStatus struct {
|
||||||
|
QuestsFound bool `json:"questsFound"`
|
||||||
|
QuestCount int `json:"questCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkQuestFiles checks if quest files exist in the bin/quests/ directory.
|
||||||
|
func checkQuestFiles(binPath string) QuestStatus {
|
||||||
|
if binPath == "" {
|
||||||
|
binPath = "bin"
|
||||||
|
}
|
||||||
|
questDir := filepath.Join(binPath, "quests")
|
||||||
|
entries, err := os.ReadDir(questDir)
|
||||||
|
if err != nil {
|
||||||
|
return QuestStatus{QuestsFound: false, QuestCount: 0}
|
||||||
|
}
|
||||||
|
count := 0
|
||||||
|
for _, e := range entries {
|
||||||
|
if !e.IsDir() {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QuestStatus{QuestsFound: count > 0, QuestCount: count}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresetInfo describes a gameplay preset for the wizard UI.
|
||||||
|
type PresetInfo struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Channels int `json:"channels"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// availablePresets returns the list of gameplay presets shown in the wizard.
|
||||||
|
func availablePresets() []PresetInfo {
|
||||||
|
return []PresetInfo{
|
||||||
|
{
|
||||||
|
ID: "solo",
|
||||||
|
Name: "Solo / Testing",
|
||||||
|
Description: "Single channel, boosted XP rates (3x), relaxed grind. Ideal for solo play or development testing.",
|
||||||
|
Channels: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "small",
|
||||||
|
Name: "Small Group (2-8 players)",
|
||||||
|
Description: "Two channels with vanilla rates. Good for friends playing together.",
|
||||||
|
Channels: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "community",
|
||||||
|
Name: "Community Server",
|
||||||
|
Description: "Full 8-channel topology with vanilla rates. Ready for a public community.",
|
||||||
|
Channels: 8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "rebalanced",
|
||||||
|
Name: "Rebalanced",
|
||||||
|
Description: "Community-tuned rates: 2x GRP, 2x HRP, extra carves. Addresses G-Rank grind without trivializing content.",
|
||||||
|
Channels: 4,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeConfig writes the config map to config.json with pretty formatting.
|
// writeConfig writes the config map to config.json with pretty formatting.
|
||||||
|
|||||||
@@ -87,12 +87,16 @@ h1{font-size:1.75rem;margin-bottom:.5rem;color:#e94560;text-align:center}
|
|||||||
<div class="progress-step" id="prog-2"></div>
|
<div class="progress-step" id="prog-2"></div>
|
||||||
<div class="progress-step" id="prog-3"></div>
|
<div class="progress-step" id="prog-3"></div>
|
||||||
<div class="progress-step" id="prog-4"></div>
|
<div class="progress-step" id="prog-4"></div>
|
||||||
|
<div class="progress-step" id="prog-5"></div>
|
||||||
|
<div class="progress-step" id="prog-6"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step-labels">
|
<div class="step-labels">
|
||||||
<span id="lbl-1">1. Database</span>
|
<span id="lbl-1">1. Database</span>
|
||||||
<span id="lbl-2">2. Schema</span>
|
<span id="lbl-2">2. Schema</span>
|
||||||
<span id="lbl-3">3. Server</span>
|
<span id="lbl-3">3. Quest Files</span>
|
||||||
<span id="lbl-4">4. Finish</span>
|
<span id="lbl-4">4. Preset</span>
|
||||||
|
<span id="lbl-5">5. Server</span>
|
||||||
|
<span id="lbl-6">6. Finish</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 1: Database Connection -->
|
<!-- Step 1: Database Connection -->
|
||||||
@@ -134,8 +138,39 @@ h1{font-size:1.75rem;margin-bottom:.5rem;color:#e94560;text-align:center}
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 3: Server Settings -->
|
<!-- Step 3: Quest Files Check -->
|
||||||
<div class="card step hidden" id="step-3">
|
<div class="card step hidden" id="step-3">
|
||||||
|
<h2>Quest Files</h2>
|
||||||
|
<p style="font-size:.85rem;color:#888;margin-bottom:1rem">Quest files are needed for gameplay. The server checks the <code>bin/quests/</code> directory.</p>
|
||||||
|
<div id="quest-status" class="status status-info">Checking quest files...</div>
|
||||||
|
<div id="quest-warning" class="hidden" style="margin-top:1rem">
|
||||||
|
<p style="font-size:.85rem;color:#e94560;margin-bottom:.7rem"><strong>No quest files found.</strong></p>
|
||||||
|
<p style="font-size:.85rem;color:#aaa;margin-bottom:.5rem">Download the quest archive:</p>
|
||||||
|
<a href="https://files.catbox.moe/xf0l7w.7z" target="_blank" rel="noopener" style="color:#4ecdc4;font-size:.9rem;word-break:break-all">https://files.catbox.moe/xf0l7w.7z</a>
|
||||||
|
<p style="font-size:.85rem;color:#aaa;margin-top:.7rem">Download the archive, extract it, and place the <code>quests/</code> and <code>scenarios/</code> folders into the <code>bin/</code> directory next to the server binary.</p>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:1rem">
|
||||||
|
<button class="btn btn-secondary" id="btn-recheck-quests" onclick="checkQuests()">Re-check</button>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="btn btn-secondary" onclick="goToStep(2)">Back</button>
|
||||||
|
<button class="btn btn-primary" id="btn-step3-next" onclick="goToStep(4)">Next</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 4: Gameplay Preset -->
|
||||||
|
<div class="card step hidden" id="step-4">
|
||||||
|
<h2>Gameplay Preset</h2>
|
||||||
|
<p style="font-size:.85rem;color:#888;margin-bottom:1rem">Choose a preset that matches your intended use. This configures channels and gameplay rates.</p>
|
||||||
|
<div id="preset-cards" style="display:flex;flex-direction:column;gap:.7rem"></div>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="btn btn-secondary" onclick="goToStep(3)">Back</button>
|
||||||
|
<button class="btn btn-primary" id="btn-step4-next" onclick="goToStep(5)">Next</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 5: Server Settings -->
|
||||||
|
<div class="card step hidden" id="step-5">
|
||||||
<h2>Server Settings</h2>
|
<h2>Server Settings</h2>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Host IP Address</label>
|
<label>Host IP Address</label>
|
||||||
@@ -162,19 +197,19 @@ h1{font-size:1.75rem;margin-bottom:.5rem;color:#e94560;text-align:center}
|
|||||||
</div>
|
</div>
|
||||||
<label class="checkbox" style="margin-top:1rem"><input type="checkbox" id="srv-auto-create" checked> Auto-create accounts (recommended for private servers)</label>
|
<label class="checkbox" style="margin-top:1rem"><input type="checkbox" id="srv-auto-create" checked> Auto-create accounts (recommended for private servers)</label>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn btn-secondary" onclick="goToStep(2)">Back</button>
|
<button class="btn btn-secondary" onclick="goToStep(4)">Back</button>
|
||||||
<button class="btn btn-primary" onclick="goToStep(4)">Next</button>
|
<button class="btn btn-primary" onclick="goToStep(6)">Next</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 4: Review & Finish -->
|
<!-- Step 6: Review & Finish -->
|
||||||
<div class="card step hidden" id="step-4">
|
<div class="card step hidden" id="step-6">
|
||||||
<h2>Review & Finish</h2>
|
<h2>Review & Finish</h2>
|
||||||
<p style="font-size:.85rem;color:#888;margin-bottom:1rem">Verify your settings before creating config.json.</p>
|
<p style="font-size:.85rem;color:#888;margin-bottom:1rem">Verify your settings before creating config.json.</p>
|
||||||
<table class="review-table" id="review-table"></table>
|
<table class="review-table" id="review-table"></table>
|
||||||
<div id="finish-status" class="hidden"></div>
|
<div id="finish-status" class="hidden"></div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn btn-secondary" onclick="goToStep(3)">Back</button>
|
<button class="btn btn-secondary" onclick="goToStep(5)">Back</button>
|
||||||
<button class="btn btn-success" id="btn-finish" onclick="finish()">Create config & Start Server</button>
|
<button class="btn btn-success" id="btn-finish" onclick="finish()">Create config & Start Server</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -184,10 +219,13 @@ h1{font-size:1.75rem;margin-bottom:.5rem;color:#e94560;text-align:center}
|
|||||||
<script>
|
<script>
|
||||||
let currentStep = 1;
|
let currentStep = 1;
|
||||||
let dbTestResult = null;
|
let dbTestResult = null;
|
||||||
|
let selectedPreset = 'community';
|
||||||
|
|
||||||
function goToStep(n) {
|
function goToStep(n) {
|
||||||
if (n === 4) buildReview();
|
if (n === 6) buildReview();
|
||||||
if (n === 2) updateSchemaOptions();
|
if (n === 2) updateSchemaOptions();
|
||||||
|
if (n === 3) checkQuests();
|
||||||
|
if (n === 4) loadPresets();
|
||||||
document.querySelectorAll('.step').forEach(el => el.classList.add('hidden'));
|
document.querySelectorAll('.step').forEach(el => el.classList.add('hidden'));
|
||||||
document.getElementById('step-' + n).classList.remove('hidden');
|
document.getElementById('step-' + n).classList.remove('hidden');
|
||||||
currentStep = n;
|
currentStep = n;
|
||||||
@@ -195,7 +233,7 @@ function goToStep(n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateProgress() {
|
function updateProgress() {
|
||||||
for (let i = 1; i <= 4; i++) {
|
for (let i = 1; i <= 6; i++) {
|
||||||
const p = document.getElementById('prog-' + i);
|
const p = document.getElementById('prog-' + i);
|
||||||
const l = document.getElementById('lbl-' + i);
|
const l = document.getElementById('lbl-' + i);
|
||||||
p.className = 'progress-step';
|
p.className = 'progress-step';
|
||||||
@@ -339,6 +377,78 @@ async function detectIP() {
|
|||||||
btn.textContent = 'Auto-detect';
|
btn.textContent = 'Auto-detect';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkQuests() {
|
||||||
|
const status = document.getElementById('quest-status');
|
||||||
|
const warning = document.getElementById('quest-warning');
|
||||||
|
const btn = document.getElementById('btn-recheck-quests');
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.innerHTML = '<span class="spinner"></span> Checking...';
|
||||||
|
status.className = 'status status-info';
|
||||||
|
status.textContent = 'Checking quest files...';
|
||||||
|
warning.classList.add('hidden');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/setup/check-quests');
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.questsFound) {
|
||||||
|
status.className = 'status status-ok';
|
||||||
|
status.textContent = data.questCount + ' quest file' + (data.questCount !== 1 ? 's' : '') + ' found.';
|
||||||
|
warning.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
status.className = 'status status-warn';
|
||||||
|
status.textContent = 'No quest files found in bin/quests/.';
|
||||||
|
warning.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
status.className = 'status status-warn';
|
||||||
|
status.textContent = 'Could not check quest files: ' + e.message;
|
||||||
|
warning.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = 'Re-check';
|
||||||
|
}
|
||||||
|
|
||||||
|
let presetsLoaded = false;
|
||||||
|
async function loadPresets() {
|
||||||
|
if (presetsLoaded) { highlightPreset(); return; }
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/setup/presets');
|
||||||
|
const data = await res.json();
|
||||||
|
const container = document.getElementById('preset-cards');
|
||||||
|
container.innerHTML = '';
|
||||||
|
data.presets.forEach(p => {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'preset-card';
|
||||||
|
card.dataset.id = p.id;
|
||||||
|
card.style.cssText = 'padding:1rem 1.2rem;background:#0f3460;border:2px solid #1a3a6e;border-radius:8px;cursor:pointer;transition:border-color .2s,background .2s';
|
||||||
|
card.innerHTML = '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:.3rem">'
|
||||||
|
+ '<strong style="color:#e0e0e0;font-size:.95rem">' + p.name + '</strong>'
|
||||||
|
+ '<span style="font-size:.75rem;color:#888">' + p.channels + ' channel' + (p.channels !== 1 ? 's' : '') + '</span>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '<div style="font-size:.82rem;color:#aaa">' + p.description + '</div>';
|
||||||
|
card.onclick = function() {
|
||||||
|
selectedPreset = p.id;
|
||||||
|
highlightPreset();
|
||||||
|
};
|
||||||
|
container.appendChild(card);
|
||||||
|
});
|
||||||
|
presetsLoaded = true;
|
||||||
|
highlightPreset();
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightPreset() {
|
||||||
|
document.querySelectorAll('.preset-card').forEach(c => {
|
||||||
|
if (c.dataset.id === selectedPreset) {
|
||||||
|
c.style.borderColor = '#e94560';
|
||||||
|
c.style.background = 'rgba(233,69,96,.1)';
|
||||||
|
} else {
|
||||||
|
c.style.borderColor = '#1a3a6e';
|
||||||
|
c.style.background = '#0f3460';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function buildReview() {
|
function buildReview() {
|
||||||
const table = document.getElementById('review-table');
|
const table = document.getElementById('review-table');
|
||||||
const password = document.getElementById('db-password').value;
|
const password = document.getElementById('db-password').value;
|
||||||
@@ -352,6 +462,7 @@ function buildReview() {
|
|||||||
['Language', document.getElementById('srv-language').value],
|
['Language', document.getElementById('srv-language').value],
|
||||||
['Client Mode', document.getElementById('srv-client-mode').value],
|
['Client Mode', document.getElementById('srv-client-mode').value],
|
||||||
['Auto-create Accounts', document.getElementById('srv-auto-create').checked ? 'Yes' : 'No'],
|
['Auto-create Accounts', document.getElementById('srv-auto-create').checked ? 'Yes' : 'No'],
|
||||||
|
['Gameplay Preset', selectedPreset],
|
||||||
];
|
];
|
||||||
table.innerHTML = rows.map(r => '<tr><td>' + r[0] + '</td><td>' + r[1] + '</td></tr>').join('');
|
table.innerHTML = rows.map(r => '<tr><td>' + r[0] + '</td><td>' + r[1] + '</td></tr>').join('');
|
||||||
}
|
}
|
||||||
@@ -376,6 +487,7 @@ async function finish() {
|
|||||||
language: document.getElementById('srv-language').value,
|
language: document.getElementById('srv-language').value,
|
||||||
clientMode: document.getElementById('srv-client-mode').value,
|
clientMode: document.getElementById('srv-client-mode').value,
|
||||||
autoCreateAccount: document.getElementById('srv-auto-create').checked,
|
autoCreateAccount: document.getElementById('srv-auto-create').checked,
|
||||||
|
preset: selectedPreset,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|||||||
@@ -3,17 +3,17 @@ package signserver
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SignCharacterRepository implements SignCharacterRepo with PostgreSQL.
|
// SignCharacterRepository implements SignCharacterRepo with PostgreSQL.
|
||||||
type SignCharacterRepository struct {
|
type SignCharacterRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSignCharacterRepository creates a new SignCharacterRepository.
|
// NewSignCharacterRepository creates a new SignCharacterRepository.
|
||||||
func NewSignCharacterRepository(db *sqlx.DB) *SignCharacterRepository {
|
func NewSignCharacterRepository(db *dbutil.DB) *SignCharacterRepository {
|
||||||
return &SignCharacterRepository{db: db}
|
return &SignCharacterRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package signserver
|
package signserver
|
||||||
|
|
||||||
import "github.com/jmoiron/sqlx"
|
import dbutil "erupe-ce/common/db"
|
||||||
|
|
||||||
// SignSessionRepository implements SignSessionRepo with PostgreSQL.
|
// SignSessionRepository implements SignSessionRepo with PostgreSQL.
|
||||||
type SignSessionRepository struct {
|
type SignSessionRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSignSessionRepository creates a new SignSessionRepository.
|
// NewSignSessionRepository creates a new SignSessionRepository.
|
||||||
func NewSignSessionRepository(db *sqlx.DB) *SignSessionRepository {
|
func NewSignSessionRepository(db *dbutil.DB) *SignSessionRepository {
|
||||||
return &SignSessionRepository{db: db}
|
return &SignSessionRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ package signserver
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
dbutil "erupe-ce/common/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SignUserRepository implements SignUserRepo with PostgreSQL.
|
// SignUserRepository implements SignUserRepo with PostgreSQL.
|
||||||
type SignUserRepository struct {
|
type SignUserRepository struct {
|
||||||
db *sqlx.DB
|
db *dbutil.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSignUserRepository creates a new SignUserRepository.
|
// NewSignUserRepository creates a new SignUserRepository.
|
||||||
func NewSignUserRepository(db *sqlx.DB) *SignUserRepository {
|
func NewSignUserRepository(db *dbutil.DB) *SignUserRepository {
|
||||||
return &SignUserRepository{db: db}
|
return &SignUserRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
dbutil "erupe-ce/common/db"
|
||||||
cfg "erupe-ce/config"
|
cfg "erupe-ce/config"
|
||||||
"erupe-ce/network"
|
"erupe-ce/network"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
@@ -38,9 +39,10 @@ func NewServer(config *Config) *Server {
|
|||||||
erupeConfig: config.ErupeConfig,
|
erupeConfig: config.ErupeConfig,
|
||||||
}
|
}
|
||||||
if config.DB != nil {
|
if config.DB != nil {
|
||||||
s.userRepo = NewSignUserRepository(config.DB)
|
wdb := dbutil.Wrap(config.DB)
|
||||||
s.charRepo = NewSignCharacterRepository(config.DB)
|
s.userRepo = NewSignUserRepository(wdb)
|
||||||
s.sessionRepo = NewSignSessionRepository(config.DB)
|
s.charRepo = NewSignCharacterRepository(wdb)
|
||||||
|
s.sessionRepo = NewSignSessionRepository(wdb)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user