diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..771099d21 --- /dev/null +++ b/.env.example @@ -0,0 +1,36 @@ +# BanManager WebUI - Environment Configuration +# Copy this file to .env and update values as needed + +# Server display name (shown in footer) +SERVER_FOOTER_NAME=BanManagement + +# Contact email for push notification registration +CONTACT_EMAIL=youremail@example.com + +# Database connection (defaults match docker-compose.yml) +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_USER=root +DB_PASSWORD=password +DB_NAME=bm_local_dev +DB_CONNECTION_LIMIT=5 + +# Security keys (generate unique values for production) +# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" +ENCRYPTION_KEY=b097b390a68441cc3bb151dd0171f25c3aabc688c50eeb26dc5e13254b333911 +SESSION_KEY=a73545a5f08d2906e39a4438014200303f9269f3ade9227525ffb141294f1b62 + +# Push notification VAPID keys (generate with: npx web-push generate-vapid-keys) +NOTIFICATION_VAPID_PUBLIC_KEY= +NOTIFICATION_VAPID_PRIVATE_KEY= + +# Admin user credentials (used by seed script and Cypress tests) +ADMIN_USERNAME=admin@banmanagement.com +ADMIN_PASSWORD=testing + +# Server configuration +PORT=3000 +LOG_LEVEL=info + +# Set to 'production' for production builds +NODE_ENV=development diff --git a/.naverc b/.naverc new file mode 100644 index 000000000..2bd5a0a98 --- /dev/null +++ b/.naverc @@ -0,0 +1 @@ +22 diff --git a/README.md b/README.md index 1dc652816..6b2e005f1 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@

## Overview + - **Always connected.** Manage punishments from anywhere with seamless logins - **Cross platform.** It doesn't matter what OS you use, it just works wherever Node.js runs - **Responsive interface.** Manage your community from any device at any time @@ -44,53 +45,152 @@ To learn more about configuration, usage and features of BanManager, take a look at [the website](https://banmanagement.com/) or view [the demo](https://demo.banmanagement.com). ## Features + - Appeal punishments - Ban, unban, mute, and warn players - Review and manage reports on the go - Custom roles and flexible permissions - A single interface for multiple Minecraft servers -## Requirements -- The latest [Node.js](https://nodejs.org/) LTS version (even numbered) +## Installation (Production) + +For deploying BanManager WebUI on your own server, see the **[full installation guide](https://banmanagement.com/docs/webui/install)**. + +### Requirements + +- [Node.js](https://nodejs.org/) LTS (v20 or v22) - MySQL v5+ or MariaDB v10+ - Minecraft server with [BanManager](https://github.com/BanManagement/BanManager) & [BanManager-WebEnhancer](https://ci.frostcast.net/job/BanManager-WebEnhancer/) plugins configured to [use MySQL or MariaDB](https://banmanagement.com/docs/banmanager/install#setup-shared-database-optional) -## Installation -See [setup instructions](https://banmanagement.com/docs/webui/install) +### Quick Install -## Development +```bash +git clone https://github.com/BanManagement/BanManager-WebUI.git +cd BanManager-WebUI +npm ci --production +npm run setup ``` + +The setup wizard will guide you through configuring your database connection and creating an admin account. + +--- + +## Development + +Want to contribute or run a local development environment? This section is for you. + +### Prerequisites + +- [Node.js](https://nodejs.org/) LTS (v20 or v22) +- [Docker](https://www.docker.com/) (for local MySQL database) + +### Quick Start + +```bash +# Clone the repository git clone git@github.com:BanManagement/BanManager-WebUI.git +cd BanManager-WebUI + +# Install dependencies npm install -npm run setup + +# Copy environment configuration +cp .env.example .env + +# Start MySQL and seed the database (first time setup) +npm run dev:setup + +# Start the development server npm run dev ``` +The application will be available at http://localhost:3000 + +### Test Accounts + +After seeding, the following accounts are available: + +| Role | Email | Password | +| ----- | ----------------------- | -------- | +| Guest | guest@banmanagement.com | testing | +| User | user@banmanagement.com | testing | +| Admin | admin@banmanagement.com | testing | + +### Available Scripts + +| Script | Description | +| -------------------- | ------------------------------------------------- | +| `npm run dev:setup` | Start MySQL container and seed the database | +| `npm run dev` | Start development server with hot reloading | +| `npm run db:start` | Start the MySQL Docker container | +| `npm run db:stop` | Stop the MySQL Docker container | +| `npm run seed` | Run migrations and seed data (fails if DB exists) | +| `npm run seed:reset` | Drop existing database and re-seed | +| `npm run build` | Build for production | +| `npm run test` | Run linting and tests | +| `npm run lint` | Run linting only | +| `npm run cypress` | Open Cypress for E2E tests | + +### Environment Configuration + +Copy `.env.example` to `.env` and adjust as needed. Key variables: + +- `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NAME` - Database connection +- `ADMIN_USERNAME`, `ADMIN_PASSWORD` - Admin account credentials (also used by Cypress) +- `ENCRYPTION_KEY`, `SESSION_KEY` - Security keys (generate unique values for production) + +### Resetting the Database + +To reset the database with fresh seed data: + +```bash +npm run seed:reset +``` + +### Running Tests + +```bash +# Run all tests +npm run test + +# Run Cypress E2E tests +npm run cypress +``` + ## Contributing + If you'd like to contribute, please fork the repository and use a feature branch. Pull requests are warmly welcome. ## Help / Bug / Feature Request + If you have found a bug please [open an issue](https://github.com/BanManagement/BanManager-WebUI/issues/new) with as much detail as possible, including relevant logs and screenshots where applicable Have an idea for a new feature? Feel free to [open an issue](https://github.com/BanManagement/BanManager-WebUI/issues/new) or [join us on Discord](https://discord.gg/59bsgZB) to chat ## License + Free to use under the [MIT](LICENSE) ## Screenshots + Click to view ### Home + [![Home](https://github.com/BanManagement/BanManager-WebUI/blob/assets/welcome.png?raw=true)](welcome.png) ### Player + [![Player](https://github.com/BanManagement/BanManager-WebUI/blob/assets/player.png?raw=true)](player.png) ### Dashboard + [![Dashboard](https://github.com/BanManagement/BanManager-WebUI/blob/assets/dashboard.png?raw=true)](dashboard.png) ### Appeal + [![Appeal](https://github.com/BanManagement/BanManager-WebUI/blob/assets/appeal.png?raw=true)](appeal.png) ### Report + [![Report](https://github.com/BanManagement/BanManager-WebUI/blob/assets/report.png?raw=true)](report.png) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..1fad94776 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +services: + mysql: + image: mysql:8.0 + container_name: banmanager-mysql + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: bm_local_dev + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-ppassword"] + interval: 5s + timeout: 5s + retries: 10 + start_period: 30s + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --default-authentication-plugin=mysql_native_password + +volumes: + mysql_data: diff --git a/package-lock.json b/package-lock.json index 4613c4d6b..4aafd48e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -109,6 +109,7 @@ "mockdate": "3.0.5", "nixt": "0.5.1", "nock": "^14.0.0-beta.6", + "nodemon": "3.1.11", "standard": "16.0.4", "standardx": "7.0.0", "supertest": "7.1.0", @@ -5024,9 +5025,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001704", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001704.tgz", - "integrity": "sha512-+L2IgBbV6gXB4ETf0keSvLr7JUrRVbIaB/lrQ1+z8mRcQiisG5k+lG6O4n6Y5q6f5EuNfaYXKgymucphlEXQew==", + "version": "1.0.30001760", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", + "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", "funding": [ { "type": "opencollective", @@ -5040,7 +5041,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/caseless": { "version": "0.12.0", @@ -8888,6 +8890,12 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "node_modules/image-q": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/image-q/-/image-q-1.1.1.tgz", @@ -11609,6 +11617,55 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" }, + "node_modules/nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -12788,6 +12845,12 @@ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -13868,6 +13931,18 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", @@ -15041,6 +15116,15 @@ "node": ">=6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tough-cookie": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -15296,6 +15380,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "node_modules/undici": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", @@ -19686,9 +19776,9 @@ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" }, "caniuse-lite": { - "version": "1.0.30001704", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001704.tgz", - "integrity": "sha512-+L2IgBbV6gXB4ETf0keSvLr7JUrRVbIaB/lrQ1+z8mRcQiisG5k+lG6O4n6Y5q6f5EuNfaYXKgymucphlEXQew==" + "version": "1.0.30001760", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", + "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==" }, "caseless": { "version": "0.12.0", @@ -22535,6 +22625,12 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "image-q": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/image-q/-/image-q-1.1.1.tgz", @@ -24520,6 +24616,41 @@ } } }, + "nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -25373,6 +25504,12 @@ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -26135,6 +26272,15 @@ } } }, + "simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "requires": { + "semver": "^7.5.3" + } + }, "sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", @@ -26976,6 +27122,12 @@ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==" }, + "touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true + }, "tough-cookie": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -27173,6 +27325,12 @@ "which-boxed-primitive": "^1.1.1" } }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "undici": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", diff --git a/package.json b/package.json index 50d27f937..80f375a4d 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "mockdate": "3.0.5", "nixt": "0.5.1", "nock": "^14.0.0-beta.6", + "nodemon": "3.1.11", "standard": "16.0.4", "standardx": "7.0.0", "supertest": "7.1.0", @@ -120,9 +121,14 @@ "scripts": { "build": "next build", "coveralls": "cat ./coverage/lcov.info | coveralls", + "db:start": "docker compose up -d", + "db:stop": "docker compose down", "dev": "npx nodemon server.js | npx pino-pretty", + "dev:setup": "npm run db:start && node scripts/seed.js", "heroku-postbuild": "npm run build && node bin/run.js update", "lint": "standardx", + "seed": "node scripts/seed.js", + "seed:reset": "node scripts/seed.js --force", "start": "node server.js", "test": "npm run lint && jest --coverage -w 1 --no-cache", "setup": "node bin/run.js setup --writeFile .env", diff --git a/scripts/seed.js b/scripts/seed.js new file mode 100644 index 000000000..9d3fe2d07 --- /dev/null +++ b/scripts/seed.js @@ -0,0 +1,299 @@ +#!/usr/bin/env node + +require('dotenv').config() + +const path = require('path') +const DBMigrate = require('db-migrate') +const { parse } = require('uuid-parse') +const { setupPool } = require('../server/connections') +const { + createServer, + createPlayer, + createBan, + createBanRecord, + createMute, + createMuteRecord, + createKick, + createNote, + createReport, + createReportComment, + createWarning, + createAppeal +} = require('../server/test/fixtures') +const createAppealComment = require('../server/test/fixtures/appeal-comment') +const { hash } = require('../server/data/hash') + +const DB_NAME = process.env.DB_NAME || 'bm_local_dev' +const FORCE = process.argv.includes('--force') + +async function waitForMySQL (config, maxRetries = 30) { + let retries = 0 + while (retries < maxRetries) { + try { + const pool = await setupPool(config) + await pool.raw('SELECT 1') + await pool.destroy() + return true + } catch (error) { + retries++ + if (retries === maxRetries) { + throw new Error(`Could not connect to MySQL after ${maxRetries} attempts. Is the database running?`) + } + console.log(`Waiting for MySQL... (attempt ${retries}/${maxRetries})`) + await new Promise(resolve => setTimeout(resolve, 2000)) + } + } +} + +async function seed () { + const dbConfig = { + host: process.env.DB_HOST || '127.0.0.1', + port: process.env.DB_PORT || 3306, + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || 'password', + multipleStatements: true + } + + console.log('Connecting to MySQL...') + await waitForMySQL(dbConfig) + + let dbPool = await setupPool(dbConfig) + + // Check if database exists + const [databases] = await dbPool.raw(`SHOW DATABASES LIKE '${DB_NAME}'`) + + if (databases.length > 0) { + if (!FORCE) { + await dbPool.destroy() + console.error(`\nError: Database '${DB_NAME}' already exists.`) + console.error('Use --force flag to drop and recreate the database.') + console.error(' npm run seed:reset') + process.exit(1) + } + + console.log(`Dropping existing database '${DB_NAME}'...`) + await dbPool.raw(`DROP DATABASE ${DB_NAME}`) + } + + console.log(`Creating database '${DB_NAME}'...`) + await dbPool.raw(`CREATE DATABASE ${DB_NAME}`) + await dbPool.destroy() + + dbConfig.database = DB_NAME + dbPool = await setupPool(dbConfig) + + // Run WebUI migrations + console.log('Running WebUI migrations...') + const dbMigrateConfig = { + connectionLimit: 1, + host: dbConfig.host, + port: dbConfig.port, + user: dbConfig.user, + password: dbConfig.password, + database: DB_NAME, + multipleStatements: true, + driver: { require: '@confuser/db-migrate-mysql' } + } + let dbmOpts = { + throwUncatched: true, + config: { dev: dbMigrateConfig }, + cmdOptions: { 'migrations-dir': path.join(__dirname, '..', 'server', 'data', 'migrations') } + } + let dbm = DBMigrate.getInstance(true, dbmOpts) + dbm.silence(true) + await dbm.up() + + // Run BanManager plugin migrations (test migrations) + console.log('Running BanManager plugin migrations...') + dbmOpts = { + throwUncatched: true, + config: { dev: dbMigrateConfig }, + cmdOptions: { 'migrations-dir': path.join(__dirname, '..', 'server', 'test', 'migrations') } + } + dbm = DBMigrate.getInstance(true, dbmOpts) + dbm.silence(true) + await dbm.up() + + console.log('Seeding data...') + + // Create console player (system account) + const playerConsole = createPlayer({ name: 'Console' }) + + // Create user accounts + const guestUser = createPlayer({ name: 'GuestPlayer' }) + const loggedInUser = createPlayer({ name: 'RegularUser' }) + const adminUser = createPlayer({ + id: parse('ae51c849-3f2a-4a37-986d-55ed5b02307f', Buffer.alloc(16)), + name: 'AdminUser' + }) + + // Create additional players for realistic data + const players = [ + createPlayer({ name: 'Griefer123' }), + createPlayer({ name: 'HackerNoob' }), + createPlayer({ name: 'ToxicPlayer' }), + createPlayer({ name: 'SpamBot' }), + createPlayer({ name: 'CheatEngine' }), + createPlayer({ name: 'RuleBreaker' }), + createPlayer({ name: 'GoodPlayer' }), + createPlayer({ name: 'NewPlayer' }), + createPlayer({ name: 'VeteranUser' }), + createPlayer({ name: 'ModHelper' }) + ] + + await dbPool('bm_players').insert([playerConsole, guestUser, loggedInUser, adminUser, ...players]) + console.log(' - Created 14 players') + + // Assign roles + await dbPool('bm_web_player_roles').insert([ + { player_id: guestUser.id, role_id: 1 }, + { player_id: loggedInUser.id, role_id: 2 }, + { player_id: adminUser.id, role_id: 3 } + ]) + console.log(' - Assigned roles') + + // Create user accounts + const adminEmail = process.env.ADMIN_USERNAME || 'admin@banmanagement.com' + const adminPassword = process.env.ADMIN_PASSWORD || 'testing' + const updated = Math.floor(Date.now() / 1000) + + await dbPool('bm_web_users').insert([ + { player_id: guestUser.id, email: 'guest@banmanagement.com', password: await hash('testing'), updated }, + { player_id: loggedInUser.id, email: 'user@banmanagement.com', password: await hash('testing'), updated }, + { player_id: adminUser.id, email: adminEmail, password: await hash(adminPassword), updated } + ]) + console.log(' - Created user accounts') + + // Create server + const server = await createServer(playerConsole.id, DB_NAME) + await dbPool('bm_web_servers').insert(server) + console.log(' - Created server connection') + + // Create active bans + const activeBans = [ + createBan(players[0], adminUser), + createBan(players[1], adminUser), + createBan(players[4], playerConsole) + ] + const insertedBans = await dbPool('bm_player_bans').insert(activeBans) + activeBans.forEach((ban, i) => { ban.id = insertedBans[0] + i }) + console.log(' - Created 3 active bans') + + // Create ban records (historical bans) + const banRecords = [ + createBanRecord(players[2], adminUser), + createBanRecord(players[3], adminUser), + createBanRecord(players[5], playerConsole), + createBanRecord(players[6], adminUser), + createBanRecord(players[7], adminUser) + ] + await dbPool('bm_player_ban_records').insert(banRecords) + console.log(' - Created 5 ban records') + + // Create active mutes + const activeMutes = [ + createMute(players[2], adminUser), + createMute(players[3], playerConsole) + ] + const insertedMutes = await dbPool('bm_player_mutes').insert(activeMutes) + activeMutes.forEach((mute, i) => { mute.id = insertedMutes[0] + i }) + console.log(' - Created 2 active mutes') + + // Create mute records + const muteRecords = [ + createMuteRecord(players[0], adminUser), + createMuteRecord(players[1], adminUser), + createMuteRecord(players[5], playerConsole) + ] + await dbPool('bm_player_mute_records').insert(muteRecords) + console.log(' - Created 3 mute records') + + // Create kicks + const kicks = [ + createKick(players[0], adminUser), + createKick(players[1], adminUser), + createKick(players[2], playerConsole), + createKick(players[3], adminUser), + createKick(players[7], adminUser) + ] + await dbPool('bm_player_kicks').insert(kicks) + console.log(' - Created 5 kicks') + + // Create warnings + const warnings = [ + createWarning(players[0], adminUser), + createWarning(players[1], adminUser), + createWarning(players[2], adminUser), + createWarning(players[5], playerConsole), + createWarning(players[6], adminUser), + createWarning(players[7], adminUser), + createWarning(players[8], adminUser) + ] + await dbPool('bm_player_warnings').insert(warnings) + console.log(' - Created 7 warnings') + + // Create notes + const notes = [ + createNote(players[0], adminUser), + createNote(players[1], adminUser), + createNote(players[5], playerConsole) + ] + await dbPool('bm_player_notes').insert(notes) + console.log(' - Created 3 notes') + + // Create reports with different states + const reports = [ + createReport(players[0], players[6], null, 1), // Open + createReport(players[1], players[7], adminUser, 2), // Assigned + createReport(players[2], players[8], null, 3), // Resolved + createReport(players[3], players[9], null, 4) // Closed + ] + const insertedReports = await dbPool('bm_player_reports').insert(reports) + const firstReportId = insertedReports[0] + console.log(' - Created 4 reports') + + // Create report comments + const reportComments = [ + createReportComment(firstReportId, adminUser), + createReportComment(firstReportId, players[6]), + createReportComment(firstReportId + 1, adminUser) + ] + await dbPool('bm_player_report_comments').insert(reportComments) + console.log(' - Created 3 report comments') + + // Create appeals with different states + const appeals = [ + createAppeal(activeBans[0], 'PlayerBan', server, players[0], null, 1), // Open + createAppeal(activeBans[1], 'PlayerBan', server, players[1], adminUser, 2), // Assigned + createAppeal(activeMutes[0], 'PlayerMute', server, players[2], null, 3), // Resolved + createAppeal(activeMutes[1], 'PlayerMute', server, players[3], null, 4) // Rejected + ] + const insertedAppeals = await dbPool('bm_web_appeals').insert(appeals) + const firstAppealId = insertedAppeals[0] + console.log(' - Created 4 appeals') + + // Create appeal comments + const appealComments = [ + createAppealComment(firstAppealId, players[0]), + createAppealComment(firstAppealId, adminUser), + createAppealComment(firstAppealId + 1, players[1]), + createAppealComment(firstAppealId + 1, adminUser) + ] + await dbPool('bm_web_appeal_comments').insert(appealComments.map(c => ({ ...c, type: 0 }))) + console.log(' - Created 4 appeal comments') + + await dbPool.destroy() + + console.log('\n✓ Database seeded successfully!') + console.log(`\nDatabase: ${DB_NAME}`) + console.log('\nTest accounts:') + console.log(' Guest: guest@banmanagement.com / testing') + console.log(' User: user@banmanagement.com / testing') + console.log(` Admin: ${adminEmail} / ${adminPassword}`) + console.log('\nStart the development server with: npm run dev') +} + +seed().catch(error => { + console.error('Seed failed:', error) + process.exit(1) +}) diff --git a/server/test/fixtures/appeal-comment.js b/server/test/fixtures/appeal-comment.js index e6c44128b..7a23de096 100644 --- a/server/test/fixtures/appeal-comment.js +++ b/server/test/fixtures/appeal-comment.js @@ -6,7 +6,7 @@ module.exports = function (appealId, actor) { return { appeal_id: appealId, actor_id: actor.id ? actor.id : actor, - comment: lorem.sentence(), + content: lorem.sentence(), created, updated: created }