diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f581ece --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.gitignore b/.gitignore index 3b1aed1..c79767b 100755 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -mongodb.log Deathwatch-Core-Rulebook.pdf data/journal/* diff --git a/README.md b/README.md index b0ff56a..6a1d468 100755 --- a/README.md +++ b/README.md @@ -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: diff --git a/data/WiredTiger b/data/WiredTiger deleted file mode 100755 index 94fbc2b..0000000 --- a/data/WiredTiger +++ /dev/null @@ -1,2 +0,0 @@ -WiredTiger -WiredTiger 10.0.2: (December 21, 2021) diff --git a/data/WiredTiger.lock b/data/WiredTiger.lock deleted file mode 100755 index 3d84206..0000000 --- a/data/WiredTiger.lock +++ /dev/null @@ -1 +0,0 @@ -WiredTiger lock file diff --git a/data/WiredTiger.turtle b/data/WiredTiger.turtle deleted file mode 100755 index 2ad5f58..0000000 --- a/data/WiredTiger.turtle +++ /dev/null @@ -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) diff --git a/data/WiredTiger.wt b/data/WiredTiger.wt deleted file mode 100755 index 776e131..0000000 Binary files a/data/WiredTiger.wt and /dev/null differ diff --git a/data/WiredTigerHS.wt b/data/WiredTigerHS.wt deleted file mode 100755 index 3f019cb..0000000 Binary files a/data/WiredTigerHS.wt and /dev/null differ diff --git a/data/_mdb_catalog.wt b/data/_mdb_catalog.wt deleted file mode 100755 index 3776352..0000000 Binary files a/data/_mdb_catalog.wt and /dev/null differ diff --git a/data/collection-0--8172258859796687419.wt b/data/collection-0--8172258859796687419.wt deleted file mode 100755 index a6bbf51..0000000 Binary files a/data/collection-0--8172258859796687419.wt and /dev/null differ diff --git a/data/collection-2--8172258859796687419.wt b/data/collection-2--8172258859796687419.wt deleted file mode 100755 index 69ec4bc..0000000 Binary files a/data/collection-2--8172258859796687419.wt and /dev/null differ diff --git a/data/collection-4--8172258859796687419.wt b/data/collection-4--8172258859796687419.wt deleted file mode 100755 index 3f019cb..0000000 Binary files a/data/collection-4--8172258859796687419.wt and /dev/null differ diff --git a/data/diagnostic.data/metrics.2025-08-14T21-20-42Z-00000 b/data/diagnostic.data/metrics.2025-08-14T21-20-42Z-00000 deleted file mode 100755 index ac441ae..0000000 Binary files a/data/diagnostic.data/metrics.2025-08-14T21-20-42Z-00000 and /dev/null differ diff --git a/data/diagnostic.data/metrics.2025-08-14T21-29-23Z-00000 b/data/diagnostic.data/metrics.2025-08-14T21-29-23Z-00000 deleted file mode 100755 index a1f0066..0000000 Binary files a/data/diagnostic.data/metrics.2025-08-14T21-29-23Z-00000 and /dev/null differ diff --git a/data/index-1--8172258859796687419.wt b/data/index-1--8172258859796687419.wt deleted file mode 100755 index fe6e9da..0000000 Binary files a/data/index-1--8172258859796687419.wt and /dev/null differ diff --git a/data/index-3--8172258859796687419.wt b/data/index-3--8172258859796687419.wt deleted file mode 100755 index e317212..0000000 Binary files a/data/index-3--8172258859796687419.wt and /dev/null differ diff --git a/data/index-5--8172258859796687419.wt b/data/index-5--8172258859796687419.wt deleted file mode 100755 index 3f019cb..0000000 Binary files a/data/index-5--8172258859796687419.wt and /dev/null differ diff --git a/data/index-6--8172258859796687419.wt b/data/index-6--8172258859796687419.wt deleted file mode 100755 index 3f019cb..0000000 Binary files a/data/index-6--8172258859796687419.wt and /dev/null differ diff --git a/data/mongod.lock b/data/mongod.lock deleted file mode 100755 index e69de29..0000000 diff --git a/data/sizeStorer.wt b/data/sizeStorer.wt deleted file mode 100755 index 69c4ad0..0000000 Binary files a/data/sizeStorer.wt and /dev/null differ diff --git a/data/storage.bson b/data/storage.bson deleted file mode 100755 index 276b694..0000000 Binary files a/data/storage.bson and /dev/null differ diff --git a/database/backend.log b/database/backend.log index 48ef207..6aea169 100644 --- a/database/backend.log +++ b/database/backend.log @@ -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 diff --git a/database/fix_player_passwords.js b/database/fix_player_passwords.js deleted file mode 100644 index e0e8a8f..0000000 --- a/database/fix_player_passwords.js +++ /dev/null @@ -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); }); diff --git a/database/gmSessionModel.js b/database/gmSessionModel.js deleted file mode 100755 index 38fe686..0000000 --- a/database/gmSessionModel.js +++ /dev/null @@ -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); diff --git a/database/migrate-to-sqlite.js b/database/migrate-to-sqlite.js deleted file mode 100644 index 273a01d..0000000 --- a/database/migrate-to-sqlite.js +++ /dev/null @@ -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 }; diff --git a/database/mongo-backup.json b/database/mongo-backup.json deleted file mode 100644 index 03e251e..0000000 --- a/database/mongo-backup.json +++ /dev/null @@ -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" - } - ] -} \ No newline at end of file diff --git a/database/playerModel.js b/database/playerModel.js deleted file mode 100755 index 7edb283..0000000 --- a/database/playerModel.js +++ /dev/null @@ -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; diff --git a/database/scripts/pm2_smoke_test.sh b/database/scripts/pm2_smoke_test.sh new file mode 100644 index 0000000..9c2ea70 --- /dev/null +++ b/database/scripts/pm2_smoke_test.sh @@ -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 diff --git a/database/server-mongo-backup.js b/database/server-mongo-backup.js deleted file mode 100755 index 12adbd5..0000000 --- a/database/server-mongo-backup.js +++ /dev/null @@ -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; diff --git a/database/sqlite/deathwatch.db-shm b/database/sqlite/deathwatch.db-shm index feefc66..00338a4 100644 Binary files a/database/sqlite/deathwatch.db-shm and b/database/sqlite/deathwatch.db-shm differ diff --git a/database/sqlite/deathwatch.db-wal b/database/sqlite/deathwatch.db-wal index 960b45c..91548cb 100644 Binary files a/database/sqlite/deathwatch.db-wal and b/database/sqlite/deathwatch.db-wal differ diff --git a/package-lock.json b/package-lock.json index b94c6dc..01552eb 100755 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 30a7d19..d01ce5e 100755 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/scripts/dbinstall.js b/scripts/dbinstall.js deleted file mode 100755 index d5371ba..0000000 --- a/scripts/dbinstall.js +++ /dev/null @@ -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(); diff --git a/scripts/install-mongodb.js b/scripts/install-mongodb.js deleted file mode 100755 index 4578fca..0000000 --- a/scripts/install-mongodb.js +++ /dev/null @@ -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(); diff --git a/src/components/PlayerTab.jsx b/src/components/PlayerTab.jsx index 431a468..b07f9ec 100755 --- a/src/components/PlayerTab.jsx +++ b/src/components/PlayerTab.jsx @@ -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 diff --git a/tests/rules.test.js b/tests/rules.test.js new file mode 100644 index 0000000..9bc5285 --- /dev/null +++ b/tests/rules.test.js @@ -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); + }); +}); diff --git a/tests/shop.test.js b/tests/shop.test.js new file mode 100644 index 0000000..fed69dc --- /dev/null +++ b/tests/shop.test.js @@ -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); + }); +});