Remove MongoDB; add PM2 scripts, CI, tests; fix PlayerTab hooks; add pm2 smoke-test

This commit is contained in:
2025-08-16 13:01:30 +02:00
parent fe3a57a9c9
commit b6320ceff6
38 changed files with 453 additions and 622 deletions

23
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test -- --runInBand
- name: Build production bundle
run: npm run build

1
.gitignore vendored
View File

@@ -21,6 +21,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
mongodb.log
Deathwatch-Core-Rulebook.pdf
data/journal/*

View File

@@ -8,7 +8,6 @@ Deathwatch Roller is a React-based application designed to assist players and Ga
- Player management
- Requisition shop for items
- Session tracking
- Integration with MongoDB for data persistence
## Setup Instructions
1. Clone the repository:

View File

@@ -1,2 +0,0 @@
WiredTiger
WiredTiger 10.0.2: (December 21, 2021)

View File

@@ -1 +0,0 @@
WiredTiger lock file

View File

@@ -1,6 +0,0 @@
WiredTiger version string
WiredTiger 10.0.2: (December 21, 2021)
WiredTiger version
major=10,minor=0,patch=2
file:WiredTiger.wt
access_pattern_hint=none,allocation_size=4KB,app_metadata=,assert=(commit_timestamp=none,durable_timestamp=none,read_timestamp=none,write_timestamp=off),block_allocation=best,block_compressor=,cache_resident=false,checksum=on,collator=,columns=,dictionary=0,encryption=(keyid=,name=),format=btree,huffman_key=,huffman_value=,id=0,ignore_in_memory_cache_size=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=4KB,key_format=S,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=32KB,leaf_value_max=0,log=(enabled=true),memory_page_image_max=0,memory_page_max=5MB,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=false,prefix_compression_min=4,readonly=false,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,tiered_object=false,tiered_storage=(auth_token=,bucket=,bucket_prefix=,cache_directory=,local_retention=300,name=,object_target_size=0),value_format=S,verbose=[],version=(major=1,minor=1),write_timestamp_usage=none,checkpoint=(WiredTigerCheckpoint.11=(addr="018581e4dc06b8b98681e4928c41608781e41f76d752808080e29fc0e22fc0",order=11,time=1755206972,size=24576,newest_start_durable_ts=0,oldest_start_ts=0,newest_txn=4,newest_stop_durable_ts=0,newest_stop_ts=-1,newest_stop_txn=-11,prepare=0,write_gen=29,run_write_gen=25)),checkpoint_backup_info=,checkpoint_lsn=(4,4864)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

View File

@@ -6663,3 +6663,308 @@ Connected to MongoDB
[2025-08-16T10:21:06.519Z] API: Deleted player testplayer
[2025-08-16T10:21:08.256Z] DB: getAllPlayers - start
[2025-08-16T10:21:08.258Z] DB: getAllPlayers - resultCount 7
[2025-08-16T10:33:58.033Z] DB: getSession - start 78e9a6aeec141cc6e5b5c7fc769bcdf001b2cd8bdc5ef7cab93ef2ceb5e76256
[2025-08-16T10:33:58.033Z] DB: getSession - ok 78e9a6aeec141cc6e5b5c7fc769bcdf001b2cd8bdc5ef7cab93ef2ceb5e76256
[2025-08-16T10:33:58.034Z] SESSION: Validate success 78e9a6aeec141cc6e5b5c7fc769bcdf001b2cd8bdc5ef7cab93ef2ceb5e76256 andreas
[2025-08-16T10:40:12.152Z] SESSION: GM bypass accepted DELETE /api/players/gmtest
[2025-08-16T10:40:12.152Z] SESSION: GM bypass accepted DELETE /api/players/gmtest
[2025-08-16T10:40:12.152Z] DB: deletePlayer - start gmtest
[2025-08-16T10:40:12.152Z] DB: deletePlayer - changes 0 gmtest
[2025-08-16T10:40:12.186Z] SESSION: GM bypass accepted POST /api/players
[2025-08-16T10:40:12.186Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:40:12.186Z] DB: getPlayerByName - not found gmtest
[2025-08-16T10:40:12.268Z] DB: createPlayer - start gmtest
[2025-08-16T10:40:12.269Z] DB: createPlayer - done gmtest rowid 85
[2025-08-16T10:40:12.269Z] API: Created player gmtest
[2025-08-16T10:40:12.288Z] SESSION: GM bypass accepted PUT /api/players/gmtest
[2025-08-16T10:40:12.288Z] SESSION: GM bypass accepted PUT /api/players/gmtest
[2025-08-16T10:40:12.288Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:40:12.289Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:40:12.289Z] DB: updatePlayer - start gmtest
[2025-08-16T10:40:12.289Z] DB: updatePlayer - changes 1 gmtest
[2025-08-16T10:40:12.289Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:40:12.289Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:40:12.290Z] API: Updated player gmtest
[2025-08-16T10:40:12.307Z] SESSION: GM bypass accepted PUT /api/players/gmtest
[2025-08-16T10:40:12.307Z] SESSION: GM bypass accepted PUT /api/players/gmtest
[2025-08-16T10:40:12.307Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:40:12.307Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:40:12.388Z] DB: updatePlayer - start gmtest
[2025-08-16T10:40:12.388Z] DB: updatePlayer - changes 1 gmtest
[2025-08-16T10:40:12.388Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:40:12.388Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:40:12.388Z] API: Updated player gmtest
[2025-08-16T10:40:12.405Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:40:12.406Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:40:12.489Z] DB: createSession - start 95266200028019a2301ce82b6fa7f5cd47f9d409ab6abebb7342caaa7eb70675 expiresAt 2025-08-17T10:40:12.489Z
[2025-08-16T10:40:12.490Z] DB: createSession - done 95266200028019a2301ce82b6fa7f5cd47f9d409ab6abebb7342caaa7eb70675
[2025-08-16T10:40:12.490Z] API: Player login gmtest 95266200028019a2301ce82b6fa7f5cd47f9d409ab6abebb7342caaa7eb70675
[2025-08-16T10:40:12.507Z] SESSION: GM bypass accepted DELETE /api/players/gmtest
[2025-08-16T10:40:12.507Z] SESSION: GM bypass accepted DELETE /api/players/gmtest
[2025-08-16T10:40:12.507Z] DB: deletePlayer - start gmtest
[2025-08-16T10:40:12.508Z] DB: deletePlayer - changes 1 gmtest
[2025-08-16T10:40:12.508Z] API: Deleted player gmtest
[2025-08-16T10:40:12.523Z] SESSION: GM bypass accepted GET /api/players
[2025-08-16T10:40:12.523Z] DB: getAllPlayers - start
[2025-08-16T10:40:12.525Z] DB: getAllPlayers - resultCount 7
[2025-08-16T10:40:12.525Z] API: Fetch all players (public) Found 7 players
[2025-08-16T10:40:14.429Z] SESSION: GM bypass accepted DELETE /api/players/testplayer
[2025-08-16T10:40:14.429Z] SESSION: GM bypass accepted DELETE /api/players/testplayer
[2025-08-16T10:40:14.429Z] DB: deletePlayer - start testplayer
[2025-08-16T10:40:14.429Z] DB: deletePlayer - changes 0 testplayer
[2025-08-16T10:40:14.445Z] SESSION: GM bypass accepted POST /api/players
[2025-08-16T10:40:14.446Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:40:14.446Z] DB: getPlayerByName - not found testplayer
[2025-08-16T10:40:14.525Z] DB: createPlayer - start testplayer
[2025-08-16T10:40:14.525Z] DB: createPlayer - done testplayer rowid 86
[2025-08-16T10:40:14.525Z] API: Created player testplayer
[2025-08-16T10:40:14.546Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:40:14.546Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:40:14.627Z] DB: createSession - start f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3 expiresAt 2025-08-17T10:40:14.627Z
[2025-08-16T10:40:14.627Z] DB: createSession - done f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3
[2025-08-16T10:40:14.627Z] API: Player login testplayer f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3
[2025-08-16T10:40:14.642Z] DB: getSession - start f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3
[2025-08-16T10:40:14.643Z] DB: getSession - ok f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3
[2025-08-16T10:40:14.643Z] SESSION: Valid f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3 testplayer PUT /api/players/testplayer
[2025-08-16T10:40:14.643Z] DB: getSession - start f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3
[2025-08-16T10:40:14.643Z] DB: getSession - ok f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3
[2025-08-16T10:40:14.643Z] SESSION: Valid f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3 testplayer PUT /api/players/testplayer
[2025-08-16T10:40:14.643Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:40:14.643Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:40:14.643Z] DB: updatePlayer - start testplayer
[2025-08-16T10:40:14.643Z] DB: updatePlayer - changes 1 testplayer
[2025-08-16T10:40:14.643Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:40:14.643Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:40:14.643Z] API: Updated player testplayer
[2025-08-16T10:40:14.670Z] DB: getSession - start f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3
[2025-08-16T10:40:14.670Z] DB: getSession - ok f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3
[2025-08-16T10:40:14.670Z] SESSION: Valid f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3 testplayer PUT /api/players/testplayer
[2025-08-16T10:40:14.670Z] DB: getSession - start f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3
[2025-08-16T10:40:14.670Z] DB: getSession - ok f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3
[2025-08-16T10:40:14.670Z] SESSION: Valid f8abbc1404903c0cf118c734972228a0a7ff2f9766da758aa0d88c7f617e84b3 testplayer PUT /api/players/testplayer
[2025-08-16T10:40:14.670Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:40:14.670Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:40:14.671Z] DB: updatePlayer - start testplayer
[2025-08-16T10:40:14.671Z] DB: updatePlayer - changes 1 testplayer
[2025-08-16T10:40:14.671Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:40:14.671Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:40:14.671Z] API: Updated player testplayer
[2025-08-16T10:40:14.688Z] SESSION: GM bypass accepted DELETE /api/players/testplayer
[2025-08-16T10:40:14.689Z] SESSION: GM bypass accepted DELETE /api/players/testplayer
[2025-08-16T10:40:14.689Z] DB: deletePlayer - start testplayer
[2025-08-16T10:40:14.689Z] DB: deletePlayer - changes 1 testplayer
[2025-08-16T10:40:14.689Z] API: Deleted player testplayer
[2025-08-16T10:40:15.178Z] DB: getAllPlayers - start
[2025-08-16T10:40:15.184Z] DB: getAllPlayers - resultCount 7
[2025-08-16T10:53:33.754Z] SESSION: GM bypass accepted DELETE /api/players/gmtest
[2025-08-16T10:53:33.755Z] SESSION: GM bypass accepted DELETE /api/players/gmtest
[2025-08-16T10:53:33.756Z] DB: deletePlayer - start gmtest
[2025-08-16T10:53:33.756Z] DB: deletePlayer - changes 0 gmtest
[2025-08-16T10:53:33.793Z] SESSION: GM bypass accepted POST /api/players
[2025-08-16T10:53:33.794Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:53:33.794Z] DB: getPlayerByName - not found gmtest
[2025-08-16T10:53:33.986Z] DB: createPlayer - start gmtest
[2025-08-16T10:53:33.986Z] DB: createPlayer - done gmtest rowid 87
[2025-08-16T10:53:33.987Z] API: Created player gmtest
[2025-08-16T10:53:34.007Z] SESSION: GM bypass accepted PUT /api/players/gmtest
[2025-08-16T10:53:34.008Z] SESSION: GM bypass accepted PUT /api/players/gmtest
[2025-08-16T10:53:34.008Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:53:34.008Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:53:34.009Z] DB: updatePlayer - start gmtest
[2025-08-16T10:53:34.010Z] DB: updatePlayer - changes 1 gmtest
[2025-08-16T10:53:34.010Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:53:34.010Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:53:34.010Z] API: Updated player gmtest
[2025-08-16T10:53:34.027Z] SESSION: GM bypass accepted PUT /api/players/gmtest
[2025-08-16T10:53:34.027Z] SESSION: GM bypass accepted PUT /api/players/gmtest
[2025-08-16T10:53:34.027Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:53:34.027Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:53:34.109Z] DB: updatePlayer - start gmtest
[2025-08-16T10:53:34.109Z] DB: updatePlayer - changes 1 gmtest
[2025-08-16T10:53:34.109Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:53:34.110Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:53:34.110Z] API: Updated player gmtest
[2025-08-16T10:53:34.127Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:53:34.127Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:53:34.207Z] DB: createSession - start b95410e923efaa4bcef57b8b3633c84ab618013d2bd6bbeb1afd99a60b96caf9 expiresAt 2025-08-17T10:53:34.207Z
[2025-08-16T10:53:34.208Z] DB: createSession - done b95410e923efaa4bcef57b8b3633c84ab618013d2bd6bbeb1afd99a60b96caf9
[2025-08-16T10:53:34.208Z] API: Player login gmtest b95410e923efaa4bcef57b8b3633c84ab618013d2bd6bbeb1afd99a60b96caf9
[2025-08-16T10:53:34.224Z] SESSION: GM bypass accepted DELETE /api/players/gmtest
[2025-08-16T10:53:34.225Z] SESSION: GM bypass accepted DELETE /api/players/gmtest
[2025-08-16T10:53:34.225Z] DB: deletePlayer - start gmtest
[2025-08-16T10:53:34.225Z] DB: deletePlayer - changes 1 gmtest
[2025-08-16T10:53:34.225Z] API: Deleted player gmtest
[2025-08-16T10:53:34.250Z] SESSION: GM bypass accepted GET /api/players
[2025-08-16T10:53:34.251Z] DB: getAllPlayers - start
[2025-08-16T10:53:34.253Z] DB: getAllPlayers - resultCount 7
[2025-08-16T10:53:34.253Z] API: Fetch all players (public) Found 7 players
[2025-08-16T10:53:34.683Z] SESSION: GM bypass accepted DELETE /api/players/shoptestplayer
[2025-08-16T10:53:34.684Z] SESSION: GM bypass accepted DELETE /api/players/shoptestplayer
[2025-08-16T10:53:34.684Z] DB: deletePlayer - start shoptestplayer
[2025-08-16T10:53:34.684Z] DB: deletePlayer - changes 0 shoptestplayer
[2025-08-16T10:53:34.697Z] SESSION: GM bypass accepted POST /api/players
[2025-08-16T10:53:34.697Z] DB: getPlayerByName - start shoptestplayer
[2025-08-16T10:53:34.698Z] DB: getPlayerByName - not found shoptestplayer
[2025-08-16T10:53:34.777Z] DB: createPlayer - start shoptestplayer
[2025-08-16T10:53:34.778Z] DB: createPlayer - done shoptestplayer rowid 88
[2025-08-16T10:53:34.778Z] API: Created player shoptestplayer
[2025-08-16T10:53:34.791Z] SESSION: Missing sessionId GET /api/shop/inventory/shoptestplayer
[2025-08-16T10:53:34.803Z] SESSION: GM bypass accepted DELETE /api/players/shoptestplayer
[2025-08-16T10:53:34.804Z] SESSION: GM bypass accepted DELETE /api/players/shoptestplayer
[2025-08-16T10:53:34.804Z] DB: deletePlayer - start shoptestplayer
[2025-08-16T10:53:34.804Z] DB: deletePlayer - changes 1 shoptestplayer
[2025-08-16T10:53:34.804Z] API: Deleted player shoptestplayer
[2025-08-16T10:53:35.117Z] SESSION: GM bypass accepted DELETE /api/players/testplayer
[2025-08-16T10:53:35.117Z] SESSION: GM bypass accepted DELETE /api/players/testplayer
[2025-08-16T10:53:35.117Z] DB: deletePlayer - start testplayer
[2025-08-16T10:53:35.118Z] DB: deletePlayer - changes 0 testplayer
[2025-08-16T10:53:35.133Z] SESSION: GM bypass accepted POST /api/players
[2025-08-16T10:53:35.134Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:53:35.134Z] DB: getPlayerByName - not found testplayer
[2025-08-16T10:53:35.216Z] DB: createPlayer - start testplayer
[2025-08-16T10:53:35.216Z] DB: createPlayer - done testplayer rowid 89
[2025-08-16T10:53:35.216Z] API: Created player testplayer
[2025-08-16T10:53:35.234Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:53:35.234Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:53:35.315Z] DB: createSession - start 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c expiresAt 2025-08-17T10:53:35.315Z
[2025-08-16T10:53:35.316Z] DB: createSession - done 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c
[2025-08-16T10:53:35.316Z] API: Player login testplayer 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c
[2025-08-16T10:53:35.332Z] DB: getSession - start 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c
[2025-08-16T10:53:35.332Z] DB: getSession - ok 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c
[2025-08-16T10:53:35.332Z] SESSION: Valid 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c testplayer PUT /api/players/testplayer
[2025-08-16T10:53:35.332Z] DB: getSession - start 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c
[2025-08-16T10:53:35.333Z] DB: getSession - ok 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c
[2025-08-16T10:53:35.333Z] SESSION: Valid 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c testplayer PUT /api/players/testplayer
[2025-08-16T10:53:35.333Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:53:35.333Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:53:35.333Z] DB: updatePlayer - start testplayer
[2025-08-16T10:53:35.333Z] DB: updatePlayer - changes 1 testplayer
[2025-08-16T10:53:35.333Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:53:35.333Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:53:35.333Z] API: Updated player testplayer
[2025-08-16T10:53:35.349Z] DB: getSession - start 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c
[2025-08-16T10:53:35.350Z] DB: getSession - ok 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c
[2025-08-16T10:53:35.350Z] SESSION: Valid 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c testplayer PUT /api/players/testplayer
[2025-08-16T10:53:35.350Z] DB: getSession - start 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c
[2025-08-16T10:53:35.350Z] DB: getSession - ok 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c
[2025-08-16T10:53:35.350Z] SESSION: Valid 195d0b602edb504111434414528c19901ecd060718a4d2db08c261cc66c69f7c testplayer PUT /api/players/testplayer
[2025-08-16T10:53:35.350Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:53:35.350Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:53:35.350Z] DB: updatePlayer - start testplayer
[2025-08-16T10:53:35.350Z] DB: updatePlayer - changes 1 testplayer
[2025-08-16T10:53:35.350Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:53:35.351Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:53:35.351Z] API: Updated player testplayer
[2025-08-16T10:53:35.366Z] SESSION: GM bypass accepted DELETE /api/players/testplayer
[2025-08-16T10:53:35.366Z] SESSION: GM bypass accepted DELETE /api/players/testplayer
[2025-08-16T10:53:35.366Z] DB: deletePlayer - start testplayer
[2025-08-16T10:53:35.367Z] DB: deletePlayer - changes 1 testplayer
[2025-08-16T10:53:35.367Z] API: Deleted player testplayer
[2025-08-16T10:53:35.902Z] DB: getAllPlayers - start
[2025-08-16T10:53:35.905Z] DB: getAllPlayers - resultCount 7
[2025-08-16T10:59:25.321Z] SESSION: GM bypass accepted DELETE /api/players/gmtest
[2025-08-16T10:59:25.322Z] SESSION: GM bypass accepted DELETE /api/players/gmtest
[2025-08-16T10:59:25.322Z] DB: deletePlayer - start gmtest
[2025-08-16T10:59:25.322Z] DB: deletePlayer - changes 0 gmtest
[2025-08-16T10:59:25.338Z] SESSION: GM bypass accepted POST /api/players
[2025-08-16T10:59:25.339Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:59:25.339Z] DB: getPlayerByName - not found gmtest
[2025-08-16T10:59:25.418Z] DB: createPlayer - start gmtest
[2025-08-16T10:59:25.418Z] DB: createPlayer - done gmtest rowid 90
[2025-08-16T10:59:25.419Z] API: Created player gmtest
[2025-08-16T10:59:25.435Z] SESSION: GM bypass accepted PUT /api/players/gmtest
[2025-08-16T10:59:25.435Z] SESSION: GM bypass accepted PUT /api/players/gmtest
[2025-08-16T10:59:25.436Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:59:25.436Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:59:25.436Z] DB: updatePlayer - start gmtest
[2025-08-16T10:59:25.436Z] DB: updatePlayer - changes 1 gmtest
[2025-08-16T10:59:25.436Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:59:25.436Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:59:25.436Z] API: Updated player gmtest
[2025-08-16T10:59:25.451Z] SESSION: GM bypass accepted PUT /api/players/gmtest
[2025-08-16T10:59:25.451Z] SESSION: GM bypass accepted PUT /api/players/gmtest
[2025-08-16T10:59:25.452Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:59:25.452Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:59:25.533Z] DB: updatePlayer - start gmtest
[2025-08-16T10:59:25.533Z] DB: updatePlayer - changes 1 gmtest
[2025-08-16T10:59:25.533Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:59:25.533Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:59:25.533Z] API: Updated player gmtest
[2025-08-16T10:59:25.549Z] DB: getPlayerByName - start gmtest
[2025-08-16T10:59:25.549Z] DB: getPlayerByName - found gmtest
[2025-08-16T10:59:25.629Z] DB: createSession - start 2936785aeb120a3bc01ceb240e7b0042612208e33770e19edaddcc0e0cb5ee2f expiresAt 2025-08-17T10:59:25.629Z
[2025-08-16T10:59:25.629Z] DB: createSession - done 2936785aeb120a3bc01ceb240e7b0042612208e33770e19edaddcc0e0cb5ee2f
[2025-08-16T10:59:25.629Z] API: Player login gmtest 2936785aeb120a3bc01ceb240e7b0042612208e33770e19edaddcc0e0cb5ee2f
[2025-08-16T10:59:25.644Z] SESSION: GM bypass accepted DELETE /api/players/gmtest
[2025-08-16T10:59:25.645Z] SESSION: GM bypass accepted DELETE /api/players/gmtest
[2025-08-16T10:59:25.645Z] DB: deletePlayer - start gmtest
[2025-08-16T10:59:25.646Z] DB: deletePlayer - changes 1 gmtest
[2025-08-16T10:59:25.646Z] API: Deleted player gmtest
[2025-08-16T10:59:25.661Z] SESSION: GM bypass accepted GET /api/players
[2025-08-16T10:59:25.661Z] DB: getAllPlayers - start
[2025-08-16T10:59:25.662Z] DB: getAllPlayers - resultCount 7
[2025-08-16T10:59:25.662Z] API: Fetch all players (public) Found 7 players
[2025-08-16T10:59:26.476Z] SESSION: GM bypass accepted DELETE /api/players/testplayer
[2025-08-16T10:59:26.476Z] SESSION: GM bypass accepted DELETE /api/players/testplayer
[2025-08-16T10:59:26.476Z] DB: deletePlayer - start testplayer
[2025-08-16T10:59:26.477Z] DB: deletePlayer - changes 0 testplayer
[2025-08-16T10:59:26.494Z] SESSION: GM bypass accepted POST /api/players
[2025-08-16T10:59:26.494Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:59:26.494Z] DB: getPlayerByName - not found testplayer
[2025-08-16T10:59:26.576Z] DB: createPlayer - start testplayer
[2025-08-16T10:59:26.576Z] DB: createPlayer - done testplayer rowid 91
[2025-08-16T10:59:26.576Z] API: Created player testplayer
[2025-08-16T10:59:26.594Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:59:26.594Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:59:26.674Z] DB: createSession - start 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9 expiresAt 2025-08-17T10:59:26.674Z
[2025-08-16T10:59:26.674Z] DB: createSession - done 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9
[2025-08-16T10:59:26.674Z] API: Player login testplayer 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9
[2025-08-16T10:59:26.690Z] DB: getSession - start 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9
[2025-08-16T10:59:26.690Z] DB: getSession - ok 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9
[2025-08-16T10:59:26.690Z] SESSION: Valid 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9 testplayer PUT /api/players/testplayer
[2025-08-16T10:59:26.691Z] DB: getSession - start 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9
[2025-08-16T10:59:26.691Z] DB: getSession - ok 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9
[2025-08-16T10:59:26.691Z] SESSION: Valid 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9 testplayer PUT /api/players/testplayer
[2025-08-16T10:59:26.691Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:59:26.691Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:59:26.691Z] DB: updatePlayer - start testplayer
[2025-08-16T10:59:26.691Z] DB: updatePlayer - changes 1 testplayer
[2025-08-16T10:59:26.691Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:59:26.691Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:59:26.692Z] API: Updated player testplayer
[2025-08-16T10:59:26.707Z] DB: getSession - start 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9
[2025-08-16T10:59:26.707Z] DB: getSession - ok 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9
[2025-08-16T10:59:26.707Z] SESSION: Valid 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9 testplayer PUT /api/players/testplayer
[2025-08-16T10:59:26.707Z] DB: getSession - start 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9
[2025-08-16T10:59:26.707Z] DB: getSession - ok 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9
[2025-08-16T10:59:26.707Z] SESSION: Valid 31676e2f35d8d1e8f1d0723c323e93afd39286a7320483214f6d07ca909066b9 testplayer PUT /api/players/testplayer
[2025-08-16T10:59:26.707Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:59:26.708Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:59:26.708Z] DB: updatePlayer - start testplayer
[2025-08-16T10:59:26.708Z] DB: updatePlayer - changes 1 testplayer
[2025-08-16T10:59:26.708Z] DB: getPlayerByName - start testplayer
[2025-08-16T10:59:26.708Z] DB: getPlayerByName - found testplayer
[2025-08-16T10:59:26.708Z] API: Updated player testplayer
[2025-08-16T10:59:26.724Z] SESSION: GM bypass accepted DELETE /api/players/testplayer
[2025-08-16T10:59:26.724Z] SESSION: GM bypass accepted DELETE /api/players/testplayer
[2025-08-16T10:59:26.724Z] DB: deletePlayer - start testplayer
[2025-08-16T10:59:26.725Z] DB: deletePlayer - changes 1 testplayer
[2025-08-16T10:59:26.725Z] API: Deleted player testplayer
[2025-08-16T10:59:27.107Z] SESSION: GM bypass accepted DELETE /api/players/shoptestplayer
[2025-08-16T10:59:27.107Z] SESSION: GM bypass accepted DELETE /api/players/shoptestplayer
[2025-08-16T10:59:27.107Z] DB: deletePlayer - start shoptestplayer
[2025-08-16T10:59:27.107Z] DB: deletePlayer - changes 0 shoptestplayer
[2025-08-16T10:59:27.121Z] SESSION: GM bypass accepted POST /api/players
[2025-08-16T10:59:27.121Z] DB: getPlayerByName - start shoptestplayer
[2025-08-16T10:59:27.121Z] DB: getPlayerByName - not found shoptestplayer
[2025-08-16T10:59:27.201Z] DB: createPlayer - start shoptestplayer
[2025-08-16T10:59:27.201Z] DB: createPlayer - done shoptestplayer rowid 92
[2025-08-16T10:59:27.201Z] API: Created player shoptestplayer
[2025-08-16T10:59:27.214Z] SESSION: Missing sessionId GET /api/shop/inventory/shoptestplayer
[2025-08-16T10:59:27.227Z] SESSION: GM bypass accepted DELETE /api/players/shoptestplayer
[2025-08-16T10:59:27.227Z] SESSION: GM bypass accepted DELETE /api/players/shoptestplayer
[2025-08-16T10:59:27.227Z] DB: deletePlayer - start shoptestplayer
[2025-08-16T10:59:27.227Z] DB: deletePlayer - changes 1 shoptestplayer
[2025-08-16T10:59:27.227Z] API: Deleted player shoptestplayer
[2025-08-16T10:59:27.726Z] DB: getAllPlayers - start
[2025-08-16T10:59:27.728Z] DB: getAllPlayers - resultCount 7

View File

@@ -1,31 +0,0 @@
// One-time script to fix player passwords at the top level
const mongoose = require('mongoose');
const Player = require('./playerModel');
const MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost:27017/deathwatch';
async function fixPasswords() {
await mongoose.connect(MONGO_URI);
const players = await Player.find();
for (const player of players) {
let pw = player.pw;
let pwHash = player.pwHash;
// Try to extract from tabInfo if not set at top level
if ((!pw || pw === '') && player.tabInfo && player.tabInfo.pw) pw = player.tabInfo.pw;
if ((!pwHash || pwHash === '') && player.tabInfo && player.tabInfo.pwHash) pwHash = player.tabInfo.pwHash;
// Try to extract from tabInfo.tabInfo (legacy nesting)
if ((!pw || pw === '') && player.tabInfo && player.tabInfo.tabInfo && player.tabInfo.tabInfo.pw) pw = player.tabInfo.tabInfo.pw;
if ((!pwHash || pwHash === '') && player.tabInfo && player.tabInfo.tabInfo && player.tabInfo.tabInfo.pwHash) pwHash = player.tabInfo.tabInfo.pwHash;
// Only update if needed
if (pw && pwHash && (player.pw !== pw || player.pwHash !== pwHash)) {
player.pw = pw;
player.pwHash = pwHash;
await player.save();
console.log(`Updated ${player.name}: pw=${pw}, pwHash=${pwHash}`);
}
}
await mongoose.disconnect();
console.log('Done.');
}
fixPasswords().catch(e => { console.error(e); process.exit(1); });

View File

@@ -1,19 +0,0 @@
const mongoose = require('mongoose');
const gmSessionSchema = new mongoose.Schema({
sessionId: {
type: String,
required: true,
unique: true,
},
isActive: {
type: Boolean,
default: true,
},
createdAt: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model('GMSession', gmSessionSchema);

View File

@@ -1,166 +0,0 @@
const mongoose = require('mongoose');
const Player = require('./playerModel');
const { playerHelpers, sessionHelpers } = require('./sqlite-db');
require('dotenv').config();
const MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost:27017/deathwatch';
async function migratePlayers() {
try {
// Connect to MongoDB
console.log('Connecting to MongoDB...');
await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });
console.log('Connected to MongoDB');
// Fetch all players from MongoDB
console.log('Fetching players from MongoDB...');
const mongoPlayers = await Player.find();
console.log(`Found ${mongoPlayers.length} players in MongoDB`);
// Migrate each player to SQLite
let migratedCount = 0;
let skippedCount = 0;
for (const mongoPlayer of mongoPlayers) {
try {
// Check if player already exists in SQLite
const existingPlayer = playerHelpers.getByName(mongoPlayer.name);
if (existingPlayer) {
console.log(`Player ${mongoPlayer.name} already exists in SQLite, updating...`);
const updated = playerHelpers.update(mongoPlayer.name, {
rollerInfo: mongoPlayer.rollerInfo || {},
shopInfo: mongoPlayer.shopInfo || {},
tabInfo: mongoPlayer.tabInfo || {},
pw: mongoPlayer.pw || '',
pwHash: mongoPlayer.pwHash || ''
});
if (updated) {
console.log(`✓ Updated player: ${mongoPlayer.name}`);
migratedCount++;
} else {
console.log(`⚠ Failed to update player: ${mongoPlayer.name}`);
skippedCount++;
}
} else {
// Create new player in SQLite
const newPlayer = playerHelpers.create({
name: mongoPlayer.name,
rollerInfo: mongoPlayer.rollerInfo || {},
shopInfo: mongoPlayer.shopInfo || {},
tabInfo: mongoPlayer.tabInfo || {},
pw: mongoPlayer.pw || '',
pwHash: mongoPlayer.pwHash || ''
});
console.log(`✓ Migrated player: ${mongoPlayer.name}`);
migratedCount++;
}
// Log the data being migrated
console.log(` - Roller info: ${Object.keys(mongoPlayer.rollerInfo || {}).length} keys`);
console.log(` - Shop info: ${Object.keys(mongoPlayer.shopInfo || {}).length} keys`);
console.log(` - Tab info: ${Object.keys(mongoPlayer.tabInfo || {}).length} keys`);
} catch (error) {
console.error(`Error migrating player ${mongoPlayer.name}:`, error);
skippedCount++;
}
}
console.log('\n=== Migration Summary ===');
console.log(`Total players in MongoDB: ${mongoPlayers.length}`);
console.log(`Successfully migrated/updated: ${migratedCount}`);
console.log(`Skipped/failed: ${skippedCount}`);
// Verify the migration
console.log('\n=== Verification ===');
const sqlitePlayers = playerHelpers.getAll();
console.log(`Total players in SQLite: ${sqlitePlayers.length}`);
// Show sample data
if (sqlitePlayers.length > 0) {
console.log('\nSample migrated player:');
const sample = sqlitePlayers[0];
console.log(`Name: ${sample.name}`);
console.log(`Roller info keys: ${Object.keys(sample.rollerInfo).length}`);
console.log(`Shop info keys: ${Object.keys(sample.shopInfo).length}`);
console.log(`Tab info keys: ${Object.keys(sample.tabInfo).length}`);
}
} catch (error) {
console.error('Migration failed:', error);
} finally {
// Close connections
await mongoose.disconnect();
console.log('Disconnected from MongoDB');
}
}
// Export player data to JSON for backup
async function exportToJson() {
try {
console.log('Exporting MongoDB data to JSON backup...');
// Connect to MongoDB
await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });
// Fetch all players
const players = await Player.find();
// Create backup object
const backup = {
exportDate: new Date().toISOString(),
playerCount: players.length,
players: players.map(player => ({
name: player.name,
rollerInfo: player.rollerInfo || {},
shopInfo: player.shopInfo || {},
tabInfo: player.tabInfo || {},
pw: player.pw || '',
pwHash: player.pwHash || '',
mongoId: player._id.toString()
}))
};
// Write to file
const fs = require('fs');
const path = require('path');
const backupPath = path.join(__dirname, 'mongo-backup.json');
fs.writeFileSync(backupPath, JSON.stringify(backup, null, 2));
console.log(`✓ Backup saved to: ${backupPath}`);
console.log(`✓ Exported ${players.length} players`);
await mongoose.disconnect();
} catch (error) {
console.error('Export failed:', error);
}
}
// Main function
async function main() {
const args = process.argv.slice(2);
if (args.includes('--export-only')) {
await exportToJson();
} else if (args.includes('--migrate-only')) {
await migratePlayers();
} else {
// Do both
await exportToJson();
await migratePlayers();
}
process.exit(0);
}
// Run if called directly
if (require.main === module) {
main().catch(console.error);
}
module.exports = { migratePlayers, exportToJson };

View File

@@ -1,45 +0,0 @@
{
"exportDate": "2025-08-15T07:12:29.894Z",
"playerCount": 3,
"players": [
{
"name": "christoffer",
"rollerInfo": {},
"shopInfo": {},
"tabInfo": {
"rp": 10,
"inventory": [],
"renown": "None"
},
"pw": "1234",
"pwHash": "1234",
"mongoId": "689e8ba7f8623eb2e1049cea"
},
{
"name": "claes",
"rollerInfo": {},
"shopInfo": {},
"tabInfo": {
"rp": 10,
"inventory": [],
"renown": "None"
},
"pw": "1234",
"pwHash": "1234",
"mongoId": "689e8ba8f8623eb2e1049cec"
},
{
"name": "phillip",
"rollerInfo": {},
"shopInfo": {},
"tabInfo": {
"rp": 10,
"inventory": [],
"renown": "None"
},
"pw": "1234",
"pwHash": "1234",
"mongoId": "689e8ba9f8623eb2e1049cee"
}
]
}

View File

@@ -1,14 +0,0 @@
const mongoose = require('mongoose');
const playerSchema = new mongoose.Schema({
name: { type: String, required: true },
rollerInfo: { type: Object, default: {} },
shopInfo: { type: Object, default: {} },
tabInfo: { type: Object, default: {} },
pw: { type: String, default: '' },
pwHash: { type: String, default: '' },
});
const Player = mongoose.model('Player', playerSchema);
module.exports = Player;

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Start pm2 using the repo pm2.config.js, wait until server is responsive, then stop pm2.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."
# start pm2 using npm script (if available) or call pm2 directly
if command -v npm >/dev/null 2>&1; then
npm run pm2:start || true
else
pm2 start pm2.config.js --env production --update-env || true
fi
# wait for server
for i in {1..15}; do
if curl -sSf http://localhost:5000/ >/dev/null 2>&1; then
echo "server up"
break
fi
sleep 1
done
# basic health endpoints
curl -sSf http://localhost:5000/api/players || true
curl -sSf http://localhost:5000/api/shop/items || true
# stop pm2 process
if command -v npm >/dev/null 2>&1; then
npm run pm2:stop || true
else
pm2 stop deathwatch-server || true
fi
exit 0

View File

@@ -1,75 +0,0 @@
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const path = require('path');
const playerRoutes = require('./routes/playerRoutes-sqlite');
const sessionRoutes = require('./routes/sessionRoutes-sqlite');
const shopRoutes = require('./routes/shopRoutes');
const rulesRoutes = require('./routes/rulesRoutes');
const app = express();
const PORT = process.env.PORT || 5000;
// Middleware
app.use(express.json());
app.use(cors());
// Serve static files from the React app build directory
app.use(express.static(path.join(__dirname, '../build')));
// Initialize SQLite database
const { db } = require('./sqlite-db');
// Graceful shutdown
process.on('SIGINT', () => {
console.log('Closing SQLite database...');
db.close();
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('Closing SQLite database...');
db.close();
process.exit(0);
});
// Root route for friendly message
app.get('/', (req, res) => {
res.send('Deathwatch Roller API is running with SQLite. Use /api/players for player data.');
});
// Health endpoint checks DB connectivity and players
app.get('/api/health', (req, res) => {
try {
const { playerHelpers } = require('./sqlite-db');
const players = playerHelpers.getAll();
res.json({ ok: true, players: players.length, sample: players.slice(0,5).map(p=>p.name) });
} catch (err) {
console.error('Healthcheck error', err);
res.status(500).json({ ok: false, error: err && err.message ? err.message : String(err) });
}
});
// Use routes
app.use('/api/players', playerRoutes);
app.use('/api/sessions', sessionRoutes);
app.use('/api/shop', shopRoutes);
app.use('/api/rules', rulesRoutes);
// Catch all handler: send back React's index.html file for any non-API routes
app.get(/^(?!\/api).*/, (req, res) => {
res.sendFile(path.join(__dirname, '../build/index.html'));
});
// Start Server
app.listen(PORT, '0.0.0.0', () => {
console.log(`Server running on http://0.0.0.0:${PORT}`);
console.log('Using SQLite database');
});
// Keep the process alive reliably under systemd (avoid accidental exit)
if (process.stdin && typeof process.stdin.resume === 'function') {
process.stdin.resume();
}
module.exports = app;

Binary file not shown.

Binary file not shown.

182
package-lock.json generated
View File

@@ -15,7 +15,6 @@
"better-sqlite3": "^12.2.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"mongoose": "^8.17.1",
"pdf-parse": "^1.1.1",
"pdfjs-dist": "^4.8.69",
"react": "^18.2.0",
@@ -4149,14 +4148,6 @@
"node": ">=10"
}
},
"node_modules/@mongodb-js/saslprep": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz",
"integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==",
"dependencies": {
"sparse-bitfield": "^3.0.3"
}
},
"node_modules/@napi-rs/canvas": {
"version": "0.1.77",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.77.tgz",
@@ -5485,19 +5476,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/webidl-conversions": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
},
"node_modules/@types/whatwg-url": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
"dependencies": {
"@types/webidl-conversions": "*"
}
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
@@ -7245,14 +7223,6 @@
"node-int64": "^0.4.0"
}
},
"node_modules/bson": {
"version": "6.10.4",
"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz",
"integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==",
"engines": {
"node": ">=16.20.1"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -15935,14 +15905,6 @@
"node": ">=4.0"
}
},
"node_modules/kareem": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
"integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -16408,11 +16370,6 @@
"node": ">= 4.0.0"
}
},
"node_modules/memory-pager": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
@@ -16800,131 +16757,6 @@
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
},
"node_modules/mongodb": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz",
"integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.9",
"bson": "^6.10.4",
"mongodb-connection-string-url": "^3.0.0"
},
"engines": {
"node": ">=16.20.1"
},
"peerDependencies": {
"@aws-sdk/credential-providers": "^3.188.0",
"@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
"mongodb-client-encryption": ">=6.0.0 <7",
"snappy": "^7.2.2",
"socks": "^2.7.1"
},
"peerDependenciesMeta": {
"@aws-sdk/credential-providers": {
"optional": true
},
"@mongodb-js/zstd": {
"optional": true
},
"gcp-metadata": {
"optional": true
},
"kerberos": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"snappy": {
"optional": true
},
"socks": {
"optional": true
}
}
},
"node_modules/mongodb-connection-string-url": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
"integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
"dependencies": {
"@types/whatwg-url": "^11.0.2",
"whatwg-url": "^14.1.0 || ^13.0.0"
}
},
"node_modules/mongodb-connection-string-url/node_modules/tr46": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"engines": {
"node": ">=12"
}
},
"node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"dependencies": {
"tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/mongoose": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.17.1.tgz",
"integrity": "sha512-aodS4cacux5caoxB5ErEwRmrafIUsVRJxHnvP7URnSUnTenr32j1qBVV+KjYxryyLSisQkxglAFF69TNLeZTLg==",
"dependencies": {
"bson": "^6.10.4",
"kareem": "2.6.3",
"mongodb": "~6.18.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
"sift": "17.1.3"
},
"engines": {
"node": ">=16.20.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mongoose"
}
},
"node_modules/mpath": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/mquery": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
"integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
"dependencies": {
"debug": "4.x"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -19986,6 +19818,7 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -21935,11 +21768,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sift": {
"version": "17.1.3",
"resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
"integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ=="
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -22133,14 +21961,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
"dependencies": {
"memory-pager": "^1.0.2"
}
},
"node_modules/spdy": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz",

View File

@@ -10,7 +10,6 @@
"better-sqlite3": "^12.2.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"mongoose": "^8.17.1",
"pdf-parse": "^1.1.1",
"pdfjs-dist": "^4.8.69",
"react": "^18.2.0",
@@ -28,11 +27,12 @@
"extract:armoury": "node scripts/extract-armoury.js \"$PWD/Deathwatch-Core-Rulebook.pdf\" public/deathwatch-armoury.json",
"server": "npm test && cd database && node server.js",
"server:notest": "cd database && node server.js",
"db:migrate": "cd database && node migrate-to-sqlite.js",
"db:export": "cd database && node migrate-to-sqlite.js --export-only",
"db:sqlite": "cd database && ./switch-db.sh sqlite",
"prestart": "",
"preserver": ""
"pm2:start": "cd database && pm2 start pm2.config.js --env production --update-env",
"pm2:stop": "pm2 stop deathwatch-server || true",
"pm2:reload": "cd database && pm2 reload pm2.config.js --env production --update-env",
"prestart": "",
"preserver": ""
},
"eslintConfig": {
"extends": [
@@ -66,6 +66,7 @@
"prettier-eslint": "^16.4.2",
"react-scripts": "^5.0.1",
"tailwindcss": "^3.3.2",
"webpack-dev-server": "^5.2.2"
"webpack-dev-server": "^5.2.2",
"pm2": "^5.2.0"
}
}

View File

@@ -1,40 +0,0 @@
const mongoose = require('mongoose');
const Player = require('../database/playerModel');
const GMSession = require('../database/gmSessionModel');
async function setupDatabase() {
try {
console.log('Connecting to MongoDB...');
await mongoose.connect('mongodb://localhost:27017/deathwatch', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('Connected to MongoDB.');
console.log('Initializing collections...');
// Initialize Players collection
const samplePlayers = [
{ name: 'Player1', rollerInfo: {}, shopInfo: {}, tabInfo: {} },
{ name: 'Player2', rollerInfo: {}, shopInfo: {}, tabInfo: {} },
];
await Player.insertMany(samplePlayers);
console.log('Players collection initialized.');
// Initialize GM Sessions collection
const sampleSessions = [
{ sessionId: 'session1', isActive: true },
{ sessionId: 'session2', isActive: false },
];
await GMSession.insertMany(sampleSessions);
console.log('GM Sessions collection initialized.');
console.log('Database setup completed successfully.');
} catch (error) {
console.error('Error during database setup:', error.message);
} finally {
mongoose.connection.close();
}
}
setupDatabase();

View File

@@ -1,26 +0,0 @@
const { execSync } = require('child_process');
function installMongoDB() {
try {
console.log('Installing gnupg and curl...');
execSync('sudo apt-get install -y gnupg curl', { stdio: 'inherit' });
console.log('Importing MongoDB public GPG key...');
execSync('curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-8.0.gpg --dearmor', { stdio: 'inherit' });
console.log('Creating MongoDB list file...');
execSync('echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list', { stdio: 'inherit' });
console.log('Reloading package database...');
execSync('sudo apt-get update', { stdio: 'inherit' });
console.log('Installing MongoDB Community Server...');
execSync('sudo apt-get install -y mongodb-org', { stdio: 'inherit' });
console.log('MongoDB installation completed successfully.');
} catch (error) {
console.error('Error during MongoDB installation:', error.message);
}
}
installMongoDB();

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useMemo } from 'react';
import { useState, useEffect, useMemo, useCallback } from 'react';
import axios from 'axios';
import logger, { debug, info, warn, logApiCall, logApiError, logUserAction } from '../utils/logger';
@@ -432,13 +432,9 @@ function PlayerTab({
// Define shopAuthed for use in the component
const shopAuthed = getShopAuthed();
function isGMOrShopAuthed() {
return isGMLoggedIn() || authedPlayer;
}
function isGMLoggedIn() {
return authedPlayer === 'gm';
}
// Stable callbacks so hooks depending on them don't change each render
const isGMLoggedIn = useCallback(() => authedPlayer === 'gm', [authedPlayer]);
const isGMOrShopAuthed = useCallback(() => isGMLoggedIn() || !!authedPlayer, [isGMLoggedIn, authedPlayer]);
// GM session is now managed in App.js

43
tests/rules.test.js Normal file
View File

@@ -0,0 +1,43 @@
jest.setTimeout(20000);
const { execSync } = require('child_process');
const path = require('path');
describe('Rules endpoints', () => {
const baseURL = process.env.API_BASE || 'http://localhost:5000';
const gmHeaders = `-H "x-gm-secret: ${process.env.GM_PASSWORD || 'bongo'}"`;
const curl = (method, url, data = null, headers = '') => {
const command = `curl -s -X ${method} ${headers} -H "Content-Type: application/json" ${data ? `-d '${JSON.stringify(data)}'` : ''} ${url}`;
try {
const result = execSync(command, { encoding: 'utf-8' });
return JSON.parse(result || '{}');
} catch (error) {
if (error.stdout) {
try { return JSON.parse(error.stdout); } catch (e) { /* ignore */ }
}
throw error;
}
};
test('categories, search, random and stats endpoints respond', () => {
const cats = curl('GET', `${baseURL}/api/rules/categories`);
expect(Array.isArray(cats)).toBe(true);
const search = curl('GET', `${baseURL}/api/rules/search?q=test`);
expect(Array.isArray(search)).toBe(true);
const randomRes = curl('GET', `${baseURL}/api/rules/random`);
expect(Array.isArray(randomRes)).toBe(true);
const stats = curl('GET', `${baseURL}/api/rules/stats`);
expect(stats && typeof stats.totalRules === 'number').toBe(true);
// Try reload without proper GM secret should fail
const reloadFail = curl('POST', `${baseURL}/api/rules/reload`);
expect(reloadFail && reloadFail.error).toBeTruthy();
// Reload with GM secret
const reloadOk = curl('POST', `${baseURL}/api/rules/reload`, null, gmHeaders);
expect(reloadOk && typeof reloadOk.success === 'boolean').toBe(true);
});
});

42
tests/shop.test.js Normal file
View File

@@ -0,0 +1,42 @@
jest.setTimeout(20000);
const { execSync } = require('child_process');
describe('Shop endpoints (public and protected)', () => {
const baseURL = process.env.API_BASE || 'http://localhost:5000';
const gmHeaders = `-H "x-gm-secret: ${process.env.GM_PASSWORD || 'bongo'}"`;
const mockPlayer = 'shoptestplayer';
const curl = (method, url, data = null, headers = '') => {
const command = `curl -s -X ${method} ${headers} -H "Content-Type: application/json" ${data ? `-d '${JSON.stringify(data)}'` : ''} ${url}`;
try {
const result = execSync(command, { encoding: 'utf-8' });
return JSON.parse(result || '{}');
} catch (error) {
if (error.stdout) {
try { return JSON.parse(error.stdout); } catch (e) { /* ignore */ }
}
throw error;
}
};
test('public items endpoints work and protected inventory requires auth', () => {
const items = curl('GET', `${baseURL}/api/shop/items`);
expect(Array.isArray(items)).toBe(true);
const category = curl('GET', `${baseURL}/api/shop/items/category/Gear`);
expect(Array.isArray(category)).toBe(true);
// create player to ensure inventory exists
try { curl('DELETE', `${baseURL}/api/players/${mockPlayer}`, null, gmHeaders); } catch (e) {}
const created = curl('POST', `${baseURL}/api/players`, { name: mockPlayer, rp: 20, pw: 'pw' }, gmHeaders);
expect(created.name).toBe(mockPlayer);
// Inventory without session should be protected (requireSession returns 403 or similar)
const invNoAuth = curl('GET', `${baseURL}/api/shop/inventory/${mockPlayer}`);
// invNoAuth should be an error object or not an array
expect(Array.isArray(invNoAuth)).toBe(false);
// cleanup
curl('DELETE', `${baseURL}/api/players/${mockPlayer}`, null, gmHeaders);
});
});