using System.Data.Common; using Dapper; using Npgsql; namespace Outnumbered.Data; // PostgreSQL backend — the PRODUCTION store (Provider="postgres"). Shares all queries with DapperPlayerRepository; // supplies only the connection (Npgsql pooling per connection string; MVCC, no WAL/busy_timeout pragmas) and the schema // DDL (BIGINT columns -> Dapper Int64 -> the same `long` row records). No drop-migration; later columns reach // existing prod tables via the ADD COLUMN IF NOT EXISTS block (CREATE TABLE IF NOT EXISTS alone never alters them). public sealed class NpgsqlRepository(string connectionString, string serverId) : DapperPlayerRepository(serverId) { private readonly string _connectionString = connectionString; protected override async Task OpenConnectionAsync() { var c = new NpgsqlConnection(_connectionString); await c.OpenAsync(); return c; } public override async Task EnsureSchemaAsync() { await using var c = await OpenConnectionAsync(); await c.ExecuteAsync( """ CREATE TABLE IF NOT EXISTS players( steamid BIGINT PRIMARY KEY, name TEXT NOT NULL DEFAULT '', xp BIGINT NOT NULL DEFAULT 0, level BIGINT NOT NULL DEFAULT 1, prestige BIGINT NOT NULL DEFAULT 0, points BIGINT NOT NULL DEFAULT 1, primary_weapon TEXT, secondary_weapon TEXT, best_wave BIGINT, gg_best_ms BIGINT, last_seen TIMESTAMPTZ); CREATE TABLE IF NOT EXISTS upgrades( steamid BIGINT NOT NULL, stat_key TEXT NOT NULL, level BIGINT NOT NULL, PRIMARY KEY(steamid, stat_key)); CREATE TABLE IF NOT EXISTS match_state( server_id TEXT NOT NULL DEFAULT 'default', steamid BIGINT NOT NULL, kills BIGINT NOT NULL DEFAULT 0, deaths BIGINT NOT NULL DEFAULT 0, streak BIGINT NOT NULL DEFAULT 0, headshot_kills BIGINT NOT NULL DEFAULT 0, gg_run_started_at BIGINT, PRIMARY KEY(server_id, steamid)); ALTER TABLE players ADD COLUMN IF NOT EXISTS best_wave BIGINT; ALTER TABLE players ADD COLUMN IF NOT EXISTS gg_best_ms BIGINT; ALTER TABLE match_state ADD COLUMN IF NOT EXISTS gg_run_started_at BIGINT; """); } }