Skip to main content

Database code review — kioskgaming_backend

FieldValue
Repokioskgaming_backend/src/database/
Review date2026-06-20
StackPostgreSQL · Sequelize ORM · custom migration runner
Scale76 models · 207 migrations · 8 partitioned tables (+ gamify logs)
Related docsFinance System · Backend code review (full) · Migration guide: kioskgaming_backend/src/database/migrations/MIGRATION_GUIDE.md

1. Summary

CriterionAssessment
Schema design (wallet, payment, hierarchy)✅ Strong — clear dual wallet, ledger with balance snapshots
Migration discipline✅ Good — migration-first, no sync() in production
Partitioning high-volume tables✅ Intentional + CLI tooling
Optimistic locking / idempotency✅ Wallet + USD ledger + payment status
Config & secrets❌ Config duplicated across 4 files; hardcoded password fallback
Model ↔ DB alignment⚠️ Some unique/index declared in model only
Actual rollback⚠️ Many data-repair migrations are irreversible
JSONB metadata⚠️ Flexible but lacks schema/version
Currency precision⚠️ DECIMAL(18,8) vs (15,2) mixed across domains
Test DB isolation⚠️ config.js test database expression has potential bug

Verdict: Production-grade foundation for fintech kiosk (ledger, partition, idempotency), but operations and governance need tightening: centralized config, partition automation, DB ↔ model constraint alignment, separate data repair from schema migration.


2. Schema overview — table groups

GroupRepresentative tablesNotes
Users & authusers, email_codes, phone_codes, user_sessions, account_otpsbcrypt hooks on User
Payment & gatewaypayment_transactions, callback_logs, payment_api_logs, transaction_events, transaction_fraud_flagsHigh volume, partitioned
CDN credits walletwallets, wallet_transactionsownerType: player/agent/shop/super_agent/platform
USD walletusd_wallets, usd_wallet_transactions, usd_wallet_reservationsgross/net + reserved; type ENUM
Online debtonline_debt_settlements, online_debt_settlement_linesPayout network/wallet metadata
Gamegame_providers, user_game_wallets, game_wallet_transactionsPer-provider balance
CDN hierarchysuper_agents, agents, shops, commission_logsbuyer_type on payment
Cashiercashiers, cashier_shifts, cashier_transactions, cashier_shift_adjustmentsCareful FK/CASCADE design (Jun 2026)
Admin & governanceadmins, admin_action_logs, admin_broadcasts, permissionsAction log partitioned
Reconciliationreconciliation_reports, reconciliation_discrepancies⚠️ Model + repo exist, not yet loaded in connection.js
Config / limitspayment_provider_fee_configs, transaction_limits_configs, vfx_configurationsFee snapshot at payment creation

Naming conventions

  • Tables: snake_case, plural, freezeTableName: true
  • DB columns: snake_case · JS fields: camelCase + field: 'snake_case'
  • Timestamps: created_at, updated_at · timezone UTC (+00:00)
  • PK: mixed — INTEGER auto-increment (payment, legacy logs) · UUID (wallet, cashier, new CDN)
  • PostgreSQL ENUM: DataTypes.ENUM(...) — common for status, type, owner_type
  • Soft delete: not used — lifecycle via status (terminated, closed, cancelled)

3. Connection & runtime

File: src/database/connection.js

AspectDetails
Pool (development)max 20, min 2, acquire 15s
Pool (production)max 8 (more conservative than dev)
Query timeoutSEQUELIZE_QUERY_TIMEOUT_MS, default 8000ms
SchemaNo sequelize.sync() — migrations only
Associations~330 lines declared centrally in connection.js
global.dbExports ~74 models + sequelize — legacy pattern, high coupling

P0 risk — credential fallback

// connection.js, migrate.js, config/config.js (development/test)
password: process.env.DB_PASSWORD || '@Linux121314'

Hardcoded password appears when env is missing — must fail fast in production/staging.

Duplicate config (4 locations)

FileUsed for
src/database/connection.jsRuntime app
src/database/migrate.jsCLI migrate up/down/status/reset
src/database/scripts/partitionManager.jsPartition ops
src/database/config/config.jsLegacy sequelize-cli / docs

Drift between files → deploy/migrate/partition may point to different DBs if one location is updated and others are not.

Potential bug — test database name

// config/config.js — wrong operator precedence
database: process.env.DB_NAME + '_test' || 'kiosk_gaming_test'
// Actual: (process.env.DB_NAME + '_test') — if DB_NAME undefined → 'undefined_test'

4. Models — patterns Sequelize

Factory: every model module.exports = (sequelize) => sequelize.define(...).

Associations: centralized in connection.js, not in individual model files. Many relationships use constraints: false — e.g. WalletTransactionPaymentTransaction via polymorphic reference_type / reference_id (no physical FK).

Hooks: primarily auth (User bcrypt, OTP/session lifecycle). No raw SQL in model files.

JSONB metadata (~29 models): heaviest on PaymentTransaction — 6 fields: metadata, callbackData, paymentResult, providerRequest, providerResponse, syncMetadata. Also on UsdWalletTransaction, CashierTransaction, API/callback logs.

Optimistic locking (version)

EntityMechanism
PaymentTransaction_updateStatusWithValidation — UPDATE WHERE id AND version
Wallet, UsdWalletupdateByIdAndVersion + retry loop in service (max 5 attempts)
Config entitiesVfxConfiguration, TransactionLimitsConfig

Models not registered in global.db

  • ReconciliationReport, ReconciliationDiscrepancy — have model + repository + core service, not imported in connection.js
  • Calling global.db.ReconciliationReport returns undefined — potential bug if worker/service not wired via bootstrap repo

5. Migrations

MetricValue
File count207 (YYYYMMDDHHMMSS-kebab-description.js)
RunnerCustom MigrationManager in migrate.js — does not use sequelize-cli runtime
TrackingSelf-managed "SequelizeMeta" table
CLInpm run migrate · migrate:status · migrate:rollback · migrate:reset
Docssrc/database/migrations/MIGRATION_GUIDE.md
Generatornpm run migrate:generate

Strengths

  • Migration-first, no production schema sync
  • Clear status/rollback CLI
  • Partial indexes for worker queries (add-worker-query-performance-indexes)
  • Seed config uses ON CONFLICT in migration (fee configs, zerox static wallet)

Risks & anti-patterns

PatternExampleRisk
Data repair in migrationrepair-zerox-*, dedupe-*, delete-legacy-*down usually no-op / irreversible
Bulk backfillwallet-transactions-type-credit-debit, CDN cutoverHard to rollback, long-running on large prod
migrate:resetDROP SCHEMA public CASCADELocal/dev only — dangerous if wrong env
Continuous ENUM value additionswithdrawal OTP, admin action log enumsPostgreSQL cannot remove ENUM values — tech debt

Recent themes (202604–202606)

  1. Cashier full schema (cashier-management-full-schema)
  2. USD wallet ledger (gross/net, reservations, purchase_credits type)
  3. Online debt settlement refactor + payout wallet/network
  4. Payment provider fee configs + ZeroX/Meld repair
  5. CDN cutover user_walletswallets
  6. Portal 2FA, withdrawal OTP, player terminate/ban
  7. Worker performance indexes

Migration governance recommendations

  • Classify header: reversible · data-only · irreversible
  • Data repair → separate idempotent script (src/database/scripts/) instead of DELETE/UPDATE in migration
  • Migration PR: must describe actual rollback (not just stub down)

6. Partitioning

Tooling: src/database/scripts/partitionManager.js + npm scripts partition:list, partition:create-year, partition:validate

8 partitioned tables (monthly RANGE on created_at):

  • payment_transactions
  • transaction_events
  • callback_logs
  • transaction_steps
  • game_provider_api_logs
  • wallet_transactions
  • game_wallet_transactions
  • admin_action_logs

Also: gamify_reward_webhook_logs (separate migration).

Reason: append-only tables, high volume — pre-create partitions 2025-01 → 2030-12 (~576 child partition tables).

Important architectural consequences

  • PostgreSQL requires UNIQUE/PK on referenced columns including partition keycannot FK directly to wallet_transactions(id) if PK is only (id)
  • Clear code comment in migration dropping credit_transactions — polymorphic reference + app-level integrity instead of FK

Operations

  • Partitions are not auto-created on app boot — cron/ops must create partitions before the new year (2031+)
  • Insert into month without partition → runtime failure

7. Idempotency & constraints (DB level)

TableConstraintSource
payment_transactions.transaction_idUNIQUEModel + migration
usd_wallet_transactions (transaction_id, type)UNIQUEMigration create-usd-wallet-transactions
wallet_transactions (transaction_id, type)UNIQUE in model index⚠️ No migration found creating corresponding constraint — verify on production DB
usd_wallet_reservations.reservation_keyUNIQUEIdempotent hold USD
payment_provider_fee_configsUNIQUE scope+provider+operation+methodMigration

Application-level idempotency

(supplement when DB lacks constraint)

  • walletService.credit — check findByTransactionIdAndType before insert
  • usdWalletService.recordAdjustment/recordReversal — unique (transactionId, type)

Partial indexes (worker)

Migration 20260414153000-add-worker-query-performance-indexes.js — pending deposits, retry queue, zerox inflight withdrawals.

Cashier integrity

  • Global unique cashiers.username
  • Partial unique: one open shift per cashier (WHERE closed_at IS NULL)

8. Data integrity & currency

Foreign keys

  • Clear FKs: cashier schema (CASCADE/RESTRICT/SET NULL), usd_wallet_transactionsusd_wallets, online debt lines
  • Sequelize FK disabled (constraints: false): log tables, polymorphic joins, admin permissions
  • Polymorphic refs: wallet_transactions.reference_type + reference_idno FK

Nullable / polymorphic

  • payment_transactions.user_id nullable — CDN buyer via buyer_type / buyer_id
  • Dual reference pattern common for ledger ↔ payment

DECIMAL precision — inconsistent

ContextPrecision
CDN wallet balance/ledgerDECIMAL(18, 8)
USD wallet gross/netDECIMAL(18, 8)
Payment amountDECIMAL(15, 2) — legacy
Game wallet transactionDECIMAL(15, 2)
Cashier physical cashDECIMAL(18, 2)
Fee ratesDECIMAL(5,4)DECIMAL(8,6) snapshot

Same monetary system but different precision → rounding risk on convert/cross-ledger; document invariant or standardize gradually.

Dual wallet systems

  • wallets (CDN credits) + usd_wallets (USD gross/net) — not merged in DB
  • Integrity depends on service layer discipline — all money movement must be within clear transaction boundaries

9. Anti-patterns

Anti-patternDetails
global.db singleton122 controller-direct-db — direct queries, hard to test
Config DB ×4Drift between connection / migrate / partition / config.js
Model ↔ migration driftUnique index in model but missing migration (wallet_transactions)
Model orphanReconciliationReport not in connection.js
JSONB without schemaProvider payload, sync state — hard to query/audit/version
Partition ops manualForgotten partition → insert outage
Irreversible migrations labeled rollback-ablemigrate:rollback misleading for data repair
Repository business logic267 baseline repository-conditional-logic
ENUM proliferationCannot remove old values
Test DB name bugconfig.js test database expression
N+1Deep include chains payment + game + user

10. Strengths

  1. Migration-first schema — production safe
  2. Partitioning with tooling + validation scripts
  3. Optimistic locking on wallet and payment status
  4. Idempotency design USD ledger (transaction_id, type)
  5. Fee/amount snapshot at payment creation — audit trail
  6. UTC enforced everywhere
  7. Production pool conservative (max 8)
  8. Carefully designed cashier schema FKs
  9. State machine payment in model (_updateStatusWithValidation)
  10. Worker-oriented partial indexes

11. Recommendations (priority)

#PriorityAction
DB-1P0Remove hardcoded DB_PASSWORD — fail fast production/staging
DB-2P0Register ReconciliationReport + ReconciliationDiscrepancy in connection.js
DB-3P0Verify + migration UNIQUE (transaction_id, type) on wallet_transactions if DB lacks it
DB-4P0Automate partition provisioning (cron create partition N+1; alert partition:validate)
DB-5P1Centralize DB config → databaseConfig.js (1 source)
DB-6P1Document dual-wallet invariant (wallets vs usd_wallets)
DB-7P1Separate data repair from schema migration
DB-8P1Standardize DECIMAL or document precision per domain
DB-9P2JSONB schema version (metadata_schema_version) + validate service layer
DB-10P2Fix config.js test database expression
DB-11P2ENUM governance — consider STRING + check constraint for values that may change
DB-12P2Reduce constraints: false for stable relationships (non-partitioned refs)
DB-13P3Migration class tag: reversible / data-only / irreversible
DB-14P3Repository refactor — pure query, logic up to service/core

12. PR checklist (database)

  • New migration has actual rollback tag (reversible / data-only / irreversible)
  • Data repair not in migration DDL if script can be separated
  • New model registered in connection.js + bootstrap/repositories.js
  • Unique/index in model has corresponding migration
  • Money field uses correct precision domain
  • JSONB metadata has rationale; consider schema version
  • Partition impact considered (insert into partitioned table)
  • Do not add hardcoded DB credentials

13. Definition of Done — database health

  • DB config single source; zero hardcoded credentials
  • ReconciliationReport models registered in connection.js
  • wallet_transactions UNIQUE (transaction_id, type) verified on production
  • Partition validate pass; year N+1 partition provisioned
  • Dual-wallet invariant documented (link Finance System)
  • Test database name fixed in config.js

14. Quick reference files

FileRole
src/database/connection.jsSequelize, pool, global.db, associations
src/database/migrate.jsCustom migration runner
src/database/scripts/partitionManager.jsPartition CRUD CLI
src/database/config/config.jsLegacy config (duplicate)
src/database/migrations/MIGRATION_GUIDE.mdMigration guide
src/database/migrations/20260121224500-convert-tables-to-partitioned.jsPartition conversion
src/database/migrations/20260608140000-cashier-management-full-schema.jsCashier schema
src/database/migrations/20260427070100-create-usd-wallet-transactions.jsUSD ledger + unique constraint
src/bootstrap/repositories.jsRepository wiring

15. Changelog

DateAuthorNotes
2026-06-20Code reviewSplit from Backend code review §8