revert: remove SQLite support

An MMO server without multiplayer defeats the purpose. PostgreSQL
is the right choice and Docker Compose already solves the setup
pain. This reverts the common/db wrapper, SQLite schema, config
Driver field, modernc.org/sqlite dependency, and all repo type
changes while keeping the dashboard, wizard, and CI improvements
from the previous commit.
This commit is contained in:
Houmgaor
2026-03-05 23:05:55 +01:00
parent ecfe58ffb4
commit ba7ec122f8
68 changed files with 222 additions and 1575 deletions

View File

@@ -1,68 +0,0 @@
// 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
}

View File

@@ -1,83 +0,0 @@
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)
}
})
}
}

View File

@@ -1,149 +0,0 @@
// 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...)
}

View File

@@ -231,12 +231,8 @@ type Course struct {
Enabled bool
}
// 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.
// Database holds the postgres database config.
type Database struct {
Driver string // "postgres" or "sqlite"
Host string
Port int
User string
@@ -458,8 +454,7 @@ func registerDefaults() {
{Name: "EXRenewing", Enabled: true},
})
// Database (Password deliberately has no default for postgres)
viper.SetDefault("Database.Driver", "postgres")
// Database (Password deliberately has no default)
viper.SetDefault("Database.Host", "localhost")
viper.SetDefault("Database.Port", 5432)
viper.SetDefault("Database.User", "postgres")

11
go.mod
View File

@@ -16,18 +16,13 @@ require (
require (
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/fsnotify/fsnotify v1.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/hashicorp/hcl v1.0.0 // 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/ncruces/go-strftime v1.0.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/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
@@ -36,13 +31,9 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/ini.v1 v1.67.0 // 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
View File

@@ -54,8 +54,6 @@ 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.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/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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -111,7 +109,6 @@ 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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
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/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=
@@ -127,8 +124,6 @@ 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/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.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.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
@@ -164,14 +159,10 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
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/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/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
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/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/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -180,8 +171,6 @@ 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
@@ -248,8 +237,6 @@ 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-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
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-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -361,7 +348,6 @@ 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-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.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/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -535,14 +521,6 @@ 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-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=
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/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

54
main.go
View File

@@ -25,7 +25,6 @@ import (
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
"go.uber.org/zap"
_ "modernc.org/sqlite"
)
// Temporary DB auto clean on startup for quick development & testing.
@@ -109,7 +108,7 @@ func main() {
logger.Info(fmt.Sprintf("Starting Erupe (9.3b-%s)", Commit()))
logger.Info(fmt.Sprintf("Client Mode: %s (%d)", config.ClientMode, config.RealClientMode))
if config.Database.Driver != "sqlite" && config.Database.Password == "" {
if config.Database.Password == "" {
preventClose(config, "Database password is blank")
}
@@ -137,38 +136,19 @@ func main() {
logger.Info("Discord: Disabled")
}
// Create the DB pool.
var db *sqlx.DB
if config.Database.Driver == "sqlite" {
dbPath := config.Database.Database
if dbPath == "" || dbPath == "erupe" {
dbPath = "erupe.db"
}
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()))
}
// SQLite only supports one writer at a time.
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)
// Create the postgres DB pool.
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()))
}
// Test the DB connection.
@@ -177,6 +157,12 @@ func main() {
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")
// Run database migrations

View File

@@ -9,8 +9,6 @@ import (
"sync"
"time"
dbutil "erupe-ce/common/db"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/jmoiron/sqlx"
@@ -48,11 +46,10 @@ func NewAPIServer(config *Config) *APIServer {
httpServer: &http.Server{},
}
if config.DB != nil {
wdb := dbutil.Wrap(config.DB)
s.userRepo = NewAPIUserRepository(wdb)
s.charRepo = NewAPICharacterRepository(wdb)
s.sessionRepo = NewAPISessionRepository(wdb)
s.eventRepo = NewAPIEventRepository(wdb)
s.userRepo = NewAPIUserRepository(config.DB)
s.charRepo = NewAPICharacterRepository(config.DB)
s.sessionRepo = NewAPISessionRepository(config.DB)
s.eventRepo = NewAPIEventRepository(config.DB)
}
return s
}

View File

@@ -3,16 +3,16 @@ package api
import (
"context"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// APICharacterRepository implements APICharacterRepo with PostgreSQL.
type APICharacterRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewAPICharacterRepository creates a new APICharacterRepository.
func NewAPICharacterRepository(db *dbutil.DB) *APICharacterRepository {
func NewAPICharacterRepository(db *sqlx.DB) *APICharacterRepository {
return &APICharacterRepository{db: db}
}

View File

@@ -6,15 +6,15 @@ import (
"errors"
"time"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
type apiEventRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewAPIEventRepository creates an APIEventRepo backed by PostgreSQL.
func NewAPIEventRepository(db *dbutil.DB) APIEventRepo {
func NewAPIEventRepository(db *sqlx.DB) APIEventRepo {
return &apiEventRepository{db: db}
}

View File

@@ -3,16 +3,16 @@ package api
import (
"context"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// APISessionRepository implements APISessionRepo with PostgreSQL.
type APISessionRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewAPISessionRepository creates a new APISessionRepository.
func NewAPISessionRepository(db *dbutil.DB) *APISessionRepository {
func NewAPISessionRepository(db *sqlx.DB) *APISessionRepository {
return &APISessionRepository{db: db}
}

View File

@@ -4,16 +4,16 @@ import (
"context"
"time"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// APIUserRepository implements APIUserRepo with PostgreSQL.
type APIUserRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewAPIUserRepository creates a new APIUserRepository.
func NewAPIUserRepository(db *dbutil.DB) *APIUserRepository {
func NewAPIUserRepository(db *sqlx.DB) *APIUserRepository {
return &APIUserRepository{db: db}
}

View File

@@ -3,16 +3,16 @@ package channelserver
import (
"fmt"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// AchievementRepository centralizes all database access for the achievements table.
type AchievementRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewAchievementRepository creates a new AchievementRepository.
func NewAchievementRepository(db *dbutil.DB) *AchievementRepository {
func NewAchievementRepository(db *sqlx.DB) *AchievementRepository {
return &AchievementRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -12,7 +11,7 @@ func setupAchievementRepo(t *testing.T) (*AchievementRepository, *sqlx.DB, uint3
db := SetupTestDB(t)
userID := CreateTestUser(t, db, "ach_test_user")
charID := CreateTestCharacter(t, db, userID, "AchChar")
repo := NewAchievementRepository(dbutil.Wrap(db))
repo := NewAchievementRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, charID
}

View File

@@ -1,16 +1,16 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// CafeRepository centralizes all database access for cafe-related tables.
type CafeRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewCafeRepository creates a new CafeRepository.
func NewCafeRepository(db *dbutil.DB) *CafeRepository {
func NewCafeRepository(db *sqlx.DB) *CafeRepository {
return &CafeRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -12,7 +11,7 @@ func setupCafeRepo(t *testing.T) (*CafeRepository, *sqlx.DB, uint32) {
db := SetupTestDB(t)
userID := CreateTestUser(t, db, "cafe_test_user")
charID := CreateTestCharacter(t, db, userID, "CafeChar")
repo := NewCafeRepository(dbutil.Wrap(db))
repo := NewCafeRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, charID
}

View File

@@ -4,16 +4,16 @@ import (
"database/sql"
"time"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// CharacterRepository centralizes all database access for the characters table.
type CharacterRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewCharacterRepository creates a new CharacterRepository.
func NewCharacterRepository(db *dbutil.DB) *CharacterRepository {
func NewCharacterRepository(db *sqlx.DB) *CharacterRepository {
return &CharacterRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"time"
@@ -13,7 +12,7 @@ func setupCharRepo(t *testing.T) (*CharacterRepository, *sqlx.DB, uint32) {
db := SetupTestDB(t)
userID := CreateTestUser(t, db, "repo_test_user")
charID := CreateTestCharacter(t, db, userID, "RepoChar")
repo := NewCharacterRepository(dbutil.Wrap(db))
repo := NewCharacterRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, charID
}

View File

@@ -1,17 +1,17 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// DistributionRepository centralizes all database access for the distribution,
// distribution_items, and distributions_accepted tables.
type DistributionRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewDistributionRepository creates a new DistributionRepository.
func NewDistributionRepository(db *dbutil.DB) *DistributionRepository {
func NewDistributionRepository(db *sqlx.DB) *DistributionRepository {
return &DistributionRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -12,7 +11,7 @@ func setupDistributionRepo(t *testing.T) (*DistributionRepository, *sqlx.DB, uin
db := SetupTestDB(t)
userID := CreateTestUser(t, db, "dist_test_user")
charID := CreateTestCharacter(t, db, userID, "DistChar")
repo := NewDistributionRepository(dbutil.Wrap(db))
repo := NewDistributionRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, charID
}

View File

@@ -1,16 +1,16 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// DivaRepository centralizes all database access for diva defense events.
type DivaRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewDivaRepository creates a new DivaRepository.
func NewDivaRepository(db *dbutil.DB) *DivaRepository {
func NewDivaRepository(db *sqlx.DB) *DivaRepository {
return &DivaRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -10,7 +9,7 @@ import (
func setupDivaRepo(t *testing.T) (*DivaRepository, *sqlx.DB) {
t.Helper()
db := SetupTestDB(t)
repo := NewDivaRepository(dbutil.Wrap(db))
repo := NewDivaRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db
}

View File

@@ -4,7 +4,7 @@ import (
"context"
"time"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// 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.
type EventRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewEventRepository creates a new EventRepository.
func NewEventRepository(db *dbutil.DB) *EventRepository {
func NewEventRepository(db *sqlx.DB) *EventRepository {
return &EventRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"time"
@@ -11,7 +10,7 @@ import (
func setupEventRepo(t *testing.T) (*EventRepository, *sqlx.DB) {
t.Helper()
db := SetupTestDB(t)
repo := NewEventRepository(dbutil.Wrap(db))
repo := NewEventRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db
}

View File

@@ -4,17 +4,17 @@ import (
"context"
"database/sql"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// FestaRepository centralizes all database access for festa-related tables
// (events, festa_registrations, festa_submissions, festa_prizes, festa_prizes_accepted, festa_trials, guild_characters).
type FestaRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewFestaRepository creates a new FestaRepository.
func NewFestaRepository(db *dbutil.DB) *FestaRepository {
func NewFestaRepository(db *sqlx.DB) *FestaRepository {
return &FestaRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"time"
@@ -14,7 +13,7 @@ func setupFestaRepo(t *testing.T) (*FestaRepository, *sqlx.DB, uint32, uint32) {
userID := CreateTestUser(t, db, "festa_test_user")
charID := CreateTestCharacter(t, db, userID, "FestaChar")
guildID := CreateTestGuild(t, db, charID, "FestaGuild")
repo := NewFestaRepository(dbutil.Wrap(db))
repo := NewFestaRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, charID, guildID
}

View File

@@ -5,17 +5,17 @@ import (
"errors"
"time"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// GachaRepository centralizes all database access for gacha-related tables
// (gacha_shop, gacha_entries, gacha_items, gacha_stepup, gacha_box).
type GachaRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewGachaRepository creates a new GachaRepository.
func NewGachaRepository(db *dbutil.DB) *GachaRepository {
func NewGachaRepository(db *sqlx.DB) *GachaRepository {
return &GachaRepository{db: db}
}

View File

@@ -3,7 +3,6 @@ package channelserver
import (
"database/sql"
"errors"
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -14,7 +13,7 @@ func setupGachaRepo(t *testing.T) (*GachaRepository, *sqlx.DB, uint32) {
db := SetupTestDB(t)
userID := CreateTestUser(t, db, "gacha_test_user")
charID := CreateTestCharacter(t, db, userID, "GachaChar")
repo := NewGachaRepository(dbutil.Wrap(db))
repo := NewGachaRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, charID
}

View File

@@ -3,16 +3,16 @@ package channelserver
import (
"fmt"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// GoocooRepository centralizes all database access for the goocoo table.
type GoocooRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewGoocooRepository creates a new GoocooRepository.
func NewGoocooRepository(db *dbutil.DB) *GoocooRepository {
func NewGoocooRepository(db *sqlx.DB) *GoocooRepository {
return &GoocooRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -12,7 +11,7 @@ func setupGoocooRepo(t *testing.T) (*GoocooRepository, *sqlx.DB, uint32) {
db := SetupTestDB(t)
userID := CreateTestUser(t, db, "goocoo_test_user")
charID := CreateTestCharacter(t, db, userID, "GoocooChar")
repo := NewGoocooRepository(dbutil.Wrap(db))
repo := NewGoocooRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, charID
}

View File

@@ -6,19 +6,17 @@ import (
"errors"
"fmt"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// GuildRepository centralizes all database access for guild-related tables
// (guilds, guild_characters, guild_applications).
type GuildRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewGuildRepository creates a new GuildRepository.
func NewGuildRepository(db *dbutil.DB) *GuildRepository {
func NewGuildRepository(db *sqlx.DB) *GuildRepository {
return &GuildRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"fmt"
"testing"
"time"
@@ -14,7 +13,7 @@ func setupGuildRepo(t *testing.T) (*GuildRepository, *sqlx.DB, uint32, uint32) {
db := SetupTestDB(t)
userID := CreateTestUser(t, db, "guild_test_user")
charID := CreateTestCharacter(t, db, userID, "GuildLeader")
repo := NewGuildRepository(dbutil.Wrap(db))
repo := NewGuildRepository(db)
guildID := CreateTestGuild(t, db, charID, "TestGuild")
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, guildID, charID
@@ -83,7 +82,7 @@ func TestGetByCharIDNotFound(t *testing.T) {
func TestCreate(t *testing.T) {
db := SetupTestDB(t)
defer TeardownTestDB(t, db)
repo := NewGuildRepository(dbutil.Wrap(db))
repo := NewGuildRepository(db)
userID := CreateTestUser(t, db, "create_guild_user")
charID := CreateTestCharacter(t, db, userID, "CreateLeader")

View File

@@ -3,17 +3,17 @@ package channelserver
import (
"fmt"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// HouseRepository centralizes all database access for house-related tables
// (user_binary house columns, warehouse, titles).
type HouseRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewHouseRepository creates a new HouseRepository.
func NewHouseRepository(db *dbutil.DB) *HouseRepository {
func NewHouseRepository(db *sqlx.DB) *HouseRepository {
return &HouseRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -13,7 +12,7 @@ func setupHouseRepo(t *testing.T) (*HouseRepository, *sqlx.DB, uint32) {
userID := CreateTestUser(t, db, "house_test_user")
charID := CreateTestCharacter(t, db, userID, "HouseChar")
CreateTestUserBinary(t, db, charID)
repo := NewHouseRepository(dbutil.Wrap(db))
repo := NewHouseRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, charID
}

View File

@@ -1,16 +1,16 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// MailRepository centralizes all database access for the mail table.
type MailRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewMailRepository creates a new MailRepository.
func NewMailRepository(db *dbutil.DB) *MailRepository {
func NewMailRepository(db *sqlx.DB) *MailRepository {
return &MailRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -14,7 +13,7 @@ func setupMailRepo(t *testing.T) (*MailRepository, *sqlx.DB, uint32, uint32) {
senderID := CreateTestCharacter(t, db, userID, "Sender")
userID2 := CreateTestUser(t, db, "mail_recipient")
recipientID := CreateTestCharacter(t, db, userID2, "Recipient")
repo := NewMailRepository(dbutil.Wrap(db))
repo := NewMailRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, senderID, recipientID
}

View File

@@ -4,16 +4,16 @@ import (
"fmt"
"time"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// MercenaryRepository centralizes database access for mercenary/rasta/airou sequences and queries.
type MercenaryRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewMercenaryRepository creates a new MercenaryRepository.
func NewMercenaryRepository(db *dbutil.DB) *MercenaryRepository {
func NewMercenaryRepository(db *sqlx.DB) *MercenaryRepository {
return &MercenaryRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -13,7 +12,7 @@ func setupMercenaryRepo(t *testing.T) (*MercenaryRepository, *sqlx.DB, uint32, u
userID := CreateTestUser(t, db, "merc_test_user")
charID := CreateTestCharacter(t, db, userID, "MercChar")
guildID := CreateTestGuild(t, db, charID, "MercGuild")
repo := NewMercenaryRepository(dbutil.Wrap(db))
repo := NewMercenaryRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, charID, guildID
}

View File

@@ -3,16 +3,16 @@ package channelserver
import (
"fmt"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// MiscRepository centralizes database access for miscellaneous game tables.
type MiscRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewMiscRepository creates a new MiscRepository.
func NewMiscRepository(db *dbutil.DB) *MiscRepository {
func NewMiscRepository(db *sqlx.DB) *MiscRepository {
return &MiscRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -10,7 +9,7 @@ import (
func setupMiscRepo(t *testing.T) (*MiscRepository, *sqlx.DB) {
t.Helper()
db := SetupTestDB(t)
repo := NewMiscRepository(dbutil.Wrap(db))
repo := NewMiscRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db
}

View File

@@ -3,16 +3,16 @@ package channelserver
import (
"fmt"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// RengokuRepository centralizes all database access for the rengoku_score table.
type RengokuRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewRengokuRepository creates a new RengokuRepository.
func NewRengokuRepository(db *dbutil.DB) *RengokuRepository {
func NewRengokuRepository(db *sqlx.DB) *RengokuRepository {
return &RengokuRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -13,7 +12,7 @@ func setupRengokuRepo(t *testing.T) (*RengokuRepository, *sqlx.DB, uint32, uint3
userID := CreateTestUser(t, db, "rengoku_test_user")
charID := CreateTestCharacter(t, db, userID, "RengokuChar")
guildID := CreateTestGuild(t, db, charID, "RengokuGuild")
repo := NewRengokuRepository(dbutil.Wrap(db))
repo := NewRengokuRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, charID, guildID
}

View File

@@ -3,16 +3,16 @@ package channelserver
import (
"fmt"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// ScenarioRepository centralizes all database access for the scenario_counter table.
type ScenarioRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewScenarioRepository creates a new ScenarioRepository.
func NewScenarioRepository(db *dbutil.DB) *ScenarioRepository {
func NewScenarioRepository(db *sqlx.DB) *ScenarioRepository {
return &ScenarioRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -10,7 +9,7 @@ import (
func setupScenarioRepo(t *testing.T) (*ScenarioRepository, *sqlx.DB) {
t.Helper()
db := SetupTestDB(t)
repo := NewScenarioRepository(dbutil.Wrap(db))
repo := NewScenarioRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db
}

View File

@@ -1,16 +1,16 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// SessionRepository centralizes all database access for sign_sessions and servers tables.
type SessionRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewSessionRepository creates a new SessionRepository.
func NewSessionRepository(db *dbutil.DB) *SessionRepository {
func NewSessionRepository(db *sqlx.DB) *SessionRepository {
return &SessionRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -14,7 +13,7 @@ func setupSessionRepo(t *testing.T) (*SessionRepository, *sqlx.DB, uint32, uint3
charID := CreateTestCharacter(t, db, userID, "SessionChar")
token := "test_token_12345"
sessionID := CreateTestSignSession(t, db, userID, token)
repo := NewSessionRepository(dbutil.Wrap(db))
repo := NewSessionRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, userID, charID, sessionID, token
}

View File

@@ -1,16 +1,16 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// ShopRepository centralizes all database access for shop-related tables.
type ShopRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewShopRepository creates a new ShopRepository.
func NewShopRepository(db *dbutil.DB) *ShopRepository {
func NewShopRepository(db *sqlx.DB) *ShopRepository {
return &ShopRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -12,7 +11,7 @@ func setupShopRepo(t *testing.T) (*ShopRepository, *sqlx.DB, uint32) {
db := SetupTestDB(t)
userID := CreateTestUser(t, db, "shop_test_user")
charID := CreateTestCharacter(t, db, userID, "ShopChar")
repo := NewShopRepository(dbutil.Wrap(db))
repo := NewShopRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, charID
}

View File

@@ -4,16 +4,16 @@ import (
"fmt"
"time"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// StampRepository centralizes all database access for the stamps table.
type StampRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewStampRepository creates a new StampRepository.
func NewStampRepository(db *dbutil.DB) *StampRepository {
func NewStampRepository(db *sqlx.DB) *StampRepository {
return &StampRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"time"
@@ -13,7 +12,7 @@ func setupStampRepo(t *testing.T) (*StampRepository, *sqlx.DB, uint32) {
db := SetupTestDB(t)
userID := CreateTestUser(t, db, "stamp_test_user")
charID := CreateTestCharacter(t, db, userID, "StampChar")
repo := NewStampRepository(dbutil.Wrap(db))
repo := NewStampRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, charID
}

View File

@@ -3,17 +3,17 @@ package channelserver
import (
"fmt"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// TowerRepository centralizes all database access for tower-related tables
// (tower, guilds tower columns, guild_characters tower columns).
type TowerRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewTowerRepository creates a new TowerRepository.
func NewTowerRepository(db *dbutil.DB) *TowerRepository {
func NewTowerRepository(db *sqlx.DB) *TowerRepository {
return &TowerRepository{db: db}
}

View File

@@ -1,7 +1,6 @@
package channelserver
import (
dbutil "erupe-ce/common/db"
"testing"
"github.com/jmoiron/sqlx"
@@ -18,7 +17,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 {
t.Fatalf("Failed to add char to guild: %v", err)
}
repo := NewTowerRepository(dbutil.Wrap(db))
repo := NewTowerRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, charID, guildID
}

View File

@@ -4,16 +4,16 @@ import (
"database/sql"
"time"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// UserRepository centralizes all database access for the users table.
type UserRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewUserRepository creates a new UserRepository.
func NewUserRepository(db *dbutil.DB) *UserRepository {
func NewUserRepository(db *sqlx.DB) *UserRepository {
return &UserRepository{db: db}
}

View File

@@ -2,7 +2,6 @@ package channelserver
import (
"database/sql"
dbutil "erupe-ce/common/db"
"testing"
"time"
@@ -13,7 +12,7 @@ func setupUserRepo(t *testing.T) (*UserRepository, *sqlx.DB, uint32) {
t.Helper()
db := SetupTestDB(t)
userID := CreateTestUser(t, db, "user_repo_test")
repo := NewUserRepository(dbutil.Wrap(db))
repo := NewUserRepository(db)
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, userID
}

View File

@@ -6,7 +6,6 @@ import (
"testing"
"time"
dbutil "erupe-ce/common/db"
"erupe-ce/common/mhfitem"
cfg "erupe-ce/config"
"erupe-ce/network/clientctx"
@@ -598,19 +597,18 @@ func createTestServerWithDB(t *testing.T, db *sqlx.DB) *Server {
server.logger = logger
// Initialize repositories
wdb := dbutil.Wrap(db)
server.charRepo = NewCharacterRepository(wdb)
server.guildRepo = NewGuildRepository(wdb)
server.userRepo = NewUserRepository(wdb)
server.gachaRepo = NewGachaRepository(wdb)
server.houseRepo = NewHouseRepository(wdb)
server.festaRepo = NewFestaRepository(wdb)
server.towerRepo = NewTowerRepository(wdb)
server.rengokuRepo = NewRengokuRepository(wdb)
server.mailRepo = NewMailRepository(wdb)
server.stampRepo = NewStampRepository(wdb)
server.distRepo = NewDistributionRepository(wdb)
server.sessionRepo = NewSessionRepository(wdb)
server.charRepo = NewCharacterRepository(db)
server.guildRepo = NewGuildRepository(db)
server.userRepo = NewUserRepository(db)
server.gachaRepo = NewGachaRepository(db)
server.houseRepo = NewHouseRepository(db)
server.festaRepo = NewFestaRepository(db)
server.towerRepo = NewTowerRepository(db)
server.rengokuRepo = NewRengokuRepository(db)
server.mailRepo = NewMailRepository(db)
server.stampRepo = NewStampRepository(db)
server.distRepo = NewDistributionRepository(db)
server.sessionRepo = NewSessionRepository(db)
return server
}

View File

@@ -11,7 +11,6 @@ import (
"time"
"erupe-ce/common/byteframe"
dbutil "erupe-ce/common/db"
cfg "erupe-ce/config"
"erupe-ce/network"
"erupe-ce/network/binpacket"
@@ -143,28 +142,27 @@ func NewServer(config *Config) *Server {
handlerTable: buildHandlerTable(),
}
wdb := dbutil.Wrap(config.DB)
s.charRepo = NewCharacterRepository(wdb)
s.guildRepo = NewGuildRepository(wdb)
s.userRepo = NewUserRepository(wdb)
s.gachaRepo = NewGachaRepository(wdb)
s.houseRepo = NewHouseRepository(wdb)
s.festaRepo = NewFestaRepository(wdb)
s.towerRepo = NewTowerRepository(wdb)
s.rengokuRepo = NewRengokuRepository(wdb)
s.mailRepo = NewMailRepository(wdb)
s.stampRepo = NewStampRepository(wdb)
s.distRepo = NewDistributionRepository(wdb)
s.sessionRepo = NewSessionRepository(wdb)
s.eventRepo = NewEventRepository(wdb)
s.achievementRepo = NewAchievementRepository(wdb)
s.shopRepo = NewShopRepository(wdb)
s.cafeRepo = NewCafeRepository(wdb)
s.goocooRepo = NewGoocooRepository(wdb)
s.divaRepo = NewDivaRepository(wdb)
s.miscRepo = NewMiscRepository(wdb)
s.scenarioRepo = NewScenarioRepository(wdb)
s.mercenaryRepo = NewMercenaryRepository(wdb)
s.charRepo = NewCharacterRepository(config.DB)
s.guildRepo = NewGuildRepository(config.DB)
s.userRepo = NewUserRepository(config.DB)
s.gachaRepo = NewGachaRepository(config.DB)
s.houseRepo = NewHouseRepository(config.DB)
s.festaRepo = NewFestaRepository(config.DB)
s.towerRepo = NewTowerRepository(config.DB)
s.rengokuRepo = NewRengokuRepository(config.DB)
s.mailRepo = NewMailRepository(config.DB)
s.stampRepo = NewStampRepository(config.DB)
s.distRepo = NewDistributionRepository(config.DB)
s.sessionRepo = NewSessionRepository(config.DB)
s.eventRepo = NewEventRepository(config.DB)
s.achievementRepo = NewAchievementRepository(config.DB)
s.shopRepo = NewShopRepository(config.DB)
s.cafeRepo = NewCafeRepository(config.DB)
s.goocooRepo = NewGoocooRepository(config.DB)
s.divaRepo = NewDivaRepository(config.DB)
s.miscRepo = NewMiscRepository(config.DB)
s.scenarioRepo = NewScenarioRepository(config.DB)
s.mercenaryRepo = NewMercenaryRepository(config.DB)
s.mailService = NewMailService(s.mailRepo, s.guildRepo, s.logger)
s.guildService = NewGuildService(s.guildRepo, s.mailService, s.charRepo, s.logger)

View File

@@ -8,7 +8,6 @@ import (
"testing"
"time"
dbutil "erupe-ce/common/db"
"erupe-ce/server/channelserver/compression/nullcomp"
"erupe-ce/server/migrations"
"github.com/jmoiron/sqlx"
@@ -331,26 +330,25 @@ func CreateTestGachaItem(t *testing.T, db *sqlx.DB, entryID uint32, itemType uin
// Use this in integration tests instead of setting s.server.db directly.
func SetTestDB(s *Server, db *sqlx.DB) {
s.db = db
wdb := dbutil.Wrap(db)
s.charRepo = NewCharacterRepository(wdb)
s.guildRepo = NewGuildRepository(wdb)
s.userRepo = NewUserRepository(wdb)
s.gachaRepo = NewGachaRepository(wdb)
s.houseRepo = NewHouseRepository(wdb)
s.festaRepo = NewFestaRepository(wdb)
s.towerRepo = NewTowerRepository(wdb)
s.rengokuRepo = NewRengokuRepository(wdb)
s.mailRepo = NewMailRepository(wdb)
s.stampRepo = NewStampRepository(wdb)
s.distRepo = NewDistributionRepository(wdb)
s.sessionRepo = NewSessionRepository(wdb)
s.eventRepo = NewEventRepository(wdb)
s.achievementRepo = NewAchievementRepository(wdb)
s.shopRepo = NewShopRepository(wdb)
s.cafeRepo = NewCafeRepository(wdb)
s.goocooRepo = NewGoocooRepository(wdb)
s.divaRepo = NewDivaRepository(wdb)
s.miscRepo = NewMiscRepository(wdb)
s.scenarioRepo = NewScenarioRepository(wdb)
s.mercenaryRepo = NewMercenaryRepository(wdb)
s.charRepo = NewCharacterRepository(db)
s.guildRepo = NewGuildRepository(db)
s.userRepo = NewUserRepository(db)
s.gachaRepo = NewGachaRepository(db)
s.houseRepo = NewHouseRepository(db)
s.festaRepo = NewFestaRepository(db)
s.towerRepo = NewTowerRepository(db)
s.rengokuRepo = NewRengokuRepository(db)
s.mailRepo = NewMailRepository(db)
s.stampRepo = NewStampRepository(db)
s.distRepo = NewDistributionRepository(db)
s.sessionRepo = NewSessionRepository(db)
s.eventRepo = NewEventRepository(db)
s.achievementRepo = NewAchievementRepository(db)
s.shopRepo = NewShopRepository(db)
s.cafeRepo = NewCafeRepository(db)
s.goocooRepo = NewGoocooRepository(db)
s.divaRepo = NewDivaRepository(db)
s.miscRepo = NewMiscRepository(db)
s.scenarioRepo = NewScenarioRepository(db)
s.mercenaryRepo = NewMercenaryRepository(db)
}

View File

@@ -8,8 +8,6 @@ import (
"strconv"
"strings"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
@@ -17,9 +15,6 @@ import (
//go:embed sql/*.sql
var migrationFS embed.FS
//go:embed sqlite/*.sql
var sqliteMigrationFS embed.FS
//go:embed seed/*.sql
var seedFS embed.FS
@@ -27,17 +22,15 @@ var seedFS embed.FS
// (auto-marks baseline as applied), then runs all pending migrations in order.
// Each migration runs in its own transaction.
func Migrate(db *sqlx.DB, logger *zap.Logger) (int, error) {
sqlite := dbutil.IsSQLite(db)
if err := ensureVersionTable(db, sqlite); err != nil {
if err := ensureVersionTable(db); err != nil {
return 0, fmt.Errorf("creating schema_version table: %w", err)
}
if err := detectExistingDB(db, logger, sqlite); err != nil {
if err := detectExistingDB(db, logger); err != nil {
return 0, fmt.Errorf("detecting existing database: %w", err)
}
migrations, err := readMigrations(sqlite)
migrations, err := readMigrations()
if err != nil {
return 0, fmt.Errorf("reading migration files: %w", err)
}
@@ -53,7 +46,7 @@ func Migrate(db *sqlx.DB, logger *zap.Logger) (int, error) {
continue
}
logger.Info(fmt.Sprintf("Applying migration %04d: %s", m.version, m.filename))
if err := applyMigration(db, m, sqlite); err != nil {
if err := applyMigration(db, m); err != nil {
return count, fmt.Errorf("applying %s: %w", m.filename, err)
}
count++
@@ -65,7 +58,6 @@ func Migrate(db *sqlx.DB, logger *zap.Logger) (int, error) {
// ApplySeedData runs all seed/*.sql files. Not tracked in schema_version.
// Safe to run multiple times if seed files use ON CONFLICT DO NOTHING.
func ApplySeedData(db *sqlx.DB, logger *zap.Logger) (int, error) {
sqlite := dbutil.IsSQLite(db)
files, err := fs.ReadDir(seedFS, "seed")
if err != nil {
return 0, fmt.Errorf("reading seed directory: %w", err)
@@ -86,11 +78,7 @@ func ApplySeedData(db *sqlx.DB, logger *zap.Logger) (int, error) {
return count, fmt.Errorf("reading seed file %s: %w", name, err)
}
logger.Info(fmt.Sprintf("Applying seed data: %s", name))
sql := string(data)
if sqlite {
sql = dbutil.Adapt(db, sql)
}
if _, err := db.Exec(sql); err != nil {
if _, err := db.Exec(string(data)); err != nil {
return count, fmt.Errorf("executing seed file %s: %w", name, err)
}
count++
@@ -100,30 +88,20 @@ func ApplySeedData(db *sqlx.DB, logger *zap.Logger) (int, error) {
// Version returns the highest applied migration number, or 0 if none.
func Version(db *sqlx.DB) (int, error) {
sqlite := dbutil.IsSQLite(db)
var exists bool
if sqlite {
err := db.QueryRow(`SELECT COUNT(*) > 0 FROM sqlite_master
WHERE type='table' AND name='schema_version'`).Scan(&exists)
if err != nil {
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
}
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 {
return 0, nil
}
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
}
@@ -133,26 +111,18 @@ type migration struct {
sql string
}
func ensureVersionTable(db *sqlx.DB, sqlite bool) error {
q := `CREATE TABLE IF NOT EXISTS schema_version (
func ensureVersionTable(db *sqlx.DB) error {
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS schema_version (
version INTEGER PRIMARY KEY,
filename TEXT NOT NULL,
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
}
// detectExistingDB checks if the database has tables but no schema_version rows.
// If so, it marks the baseline migration (version 1) as already applied.
func detectExistingDB(db *sqlx.DB, logger *zap.Logger, sqlite bool) error {
func detectExistingDB(db *sqlx.DB, logger *zap.Logger) error {
var count int
if err := db.QueryRow("SELECT COUNT(*) FROM schema_version").Scan(&count); err != nil {
return err
@@ -163,18 +133,10 @@ func detectExistingDB(db *sqlx.DB, logger *zap.Logger, sqlite bool) error {
// Check if the database has any user tables (beyond schema_version itself)
var tableCount int
if sqlite {
err := db.QueryRow(`SELECT COUNT(*) FROM sqlite_master
WHERE type='table' AND name != 'schema_version'`).Scan(&tableCount)
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
}
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 {
return nil // Fresh database
@@ -182,22 +144,12 @@ func detectExistingDB(db *sqlx.DB, logger *zap.Logger, sqlite bool) error {
// Existing database without migration tracking — mark 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
}
func readMigrations(sqlite bool) ([]migration, error) {
var embedFS embed.FS
var dir string
if sqlite {
embedFS = sqliteMigrationFS
dir = "sqlite"
} else {
embedFS = migrationFS
dir = "sql"
}
files, err := fs.ReadDir(embedFS, dir)
func readMigrations() ([]migration, error) {
files, err := fs.ReadDir(migrationFS, "sql")
if err != nil {
return nil, err
}
@@ -211,7 +163,7 @@ func readMigrations(sqlite bool) ([]migration, error) {
if err != nil {
return nil, fmt.Errorf("parsing version from %s: %w", f.Name(), err)
}
data, err := embedFS.ReadFile(dir + "/" + f.Name())
data, err := migrationFS.ReadFile("sql/" + f.Name())
if err != nil {
return nil, err
}
@@ -254,7 +206,7 @@ func appliedVersions(db *sqlx.DB) (map[int]bool, error) {
return applied, rows.Err()
}
func applyMigration(db *sqlx.DB, m migration, sqlite bool) error {
func applyMigration(db *sqlx.DB, m migration) error {
tx, err := db.Begin()
if err != nil {
return err
@@ -265,11 +217,10 @@ func applyMigration(db *sqlx.DB, m migration, sqlite bool) error {
return err
}
insertQ := "INSERT INTO schema_version (version, filename) VALUES ($1, $2)"
if sqlite {
insertQ = "INSERT INTO schema_version (version, filename) VALUES (?, ?)"
}
if _, err := tx.Exec(insertQ, m.version, m.filename); err != nil {
if _, err := tx.Exec(
"INSERT INTO schema_version (version, filename) VALUES ($1, $2)",
m.version, m.filename,
); err != nil {
_ = tx.Rollback()
return err
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
"go.uber.org/zap"
_ "modernc.org/sqlite"
)
func testDB(t *testing.T) *sqlx.DB {
@@ -195,7 +194,7 @@ func TestParseVersion(t *testing.T) {
}
func TestReadMigrations(t *testing.T) {
migrations, err := readMigrations(false)
migrations, err := readMigrations()
if err != nil {
t.Fatalf("readMigrations failed: %v", err)
}
@@ -211,7 +210,7 @@ func TestReadMigrations(t *testing.T) {
}
func TestReadMigrations_Sorted(t *testing.T) {
migrations, err := readMigrations(false)
migrations, err := readMigrations()
if err != nil {
t.Fatalf("readMigrations failed: %v", err)
}
@@ -224,7 +223,7 @@ func TestReadMigrations_Sorted(t *testing.T) {
}
func TestReadMigrations_AllHaveSQL(t *testing.T) {
migrations, err := readMigrations(false)
migrations, err := readMigrations()
if err != nil {
t.Fatalf("readMigrations failed: %v", err)
}
@@ -236,7 +235,7 @@ func TestReadMigrations_AllHaveSQL(t *testing.T) {
}
func TestReadMigrations_BaselineIsLargest(t *testing.T) {
migrations, err := readMigrations(false)
migrations, err := readMigrations()
if err != nil {
t.Fatalf("readMigrations failed: %v", err)
}
@@ -253,61 +252,6 @@ 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) {
tests := []struct {
filename string

View File

@@ -1,855 +0,0 @@
-- 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);

View File

@@ -1,3 +0,0 @@
-- 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;

View File

@@ -1,2 +0,0 @@
-- SQLite: no-op. The unique index is already in 0001_init.sql.
SELECT 1;

View File

@@ -1,2 +0,0 @@
-- SQLite: no-op. The recruiting column is already in 0001_init.sql.
SELECT 1;

View File

@@ -1,2 +0,0 @@
-- SQLite: no-op. The data column was never included in 0001_init.sql.
SELECT 1;

View File

@@ -3,17 +3,17 @@ package signserver
import (
"strings"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
)
// SignCharacterRepository implements SignCharacterRepo with PostgreSQL.
type SignCharacterRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewSignCharacterRepository creates a new SignCharacterRepository.
func NewSignCharacterRepository(db *dbutil.DB) *SignCharacterRepository {
func NewSignCharacterRepository(db *sqlx.DB) *SignCharacterRepository {
return &SignCharacterRepository{db: db}
}

View File

@@ -1,14 +1,14 @@
package signserver
import dbutil "erupe-ce/common/db"
import "github.com/jmoiron/sqlx"
// SignSessionRepository implements SignSessionRepo with PostgreSQL.
type SignSessionRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewSignSessionRepository creates a new SignSessionRepository.
func NewSignSessionRepository(db *dbutil.DB) *SignSessionRepository {
func NewSignSessionRepository(db *sqlx.DB) *SignSessionRepository {
return &SignSessionRepository{db: db}
}

View File

@@ -3,16 +3,16 @@ package signserver
import (
"time"
dbutil "erupe-ce/common/db"
"github.com/jmoiron/sqlx"
)
// SignUserRepository implements SignUserRepo with PostgreSQL.
type SignUserRepository struct {
db *dbutil.DB
db *sqlx.DB
}
// NewSignUserRepository creates a new SignUserRepository.
func NewSignUserRepository(db *dbutil.DB) *SignUserRepository {
func NewSignUserRepository(db *sqlx.DB) *SignUserRepository {
return &SignUserRepository{db: db}
}

View File

@@ -6,7 +6,6 @@ import (
"net"
"sync"
dbutil "erupe-ce/common/db"
cfg "erupe-ce/config"
"erupe-ce/network"
"github.com/jmoiron/sqlx"
@@ -39,10 +38,9 @@ func NewServer(config *Config) *Server {
erupeConfig: config.ErupeConfig,
}
if config.DB != nil {
wdb := dbutil.Wrap(config.DB)
s.userRepo = NewSignUserRepository(wdb)
s.charRepo = NewSignCharacterRepository(wdb)
s.sessionRepo = NewSignSessionRepository(wdb)
s.userRepo = NewSignUserRepository(config.DB)
s.charRepo = NewSignCharacterRepository(config.DB)
s.sessionRepo = NewSignSessionRepository(config.DB)
}
return s
}