From 57acd761f9045d0c11e850297319a253c938ad71 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 15 Aug 2025 12:19:41 +0200 Subject: [PATCH] test1 --- database/backend.log | 403 +++++++++++++++++++++++++ database/requireSession.js | 60 ++-- database/routes/playerRoutes-sqlite.js | 118 ++++---- database/sqlite-db.js | 58 +++- database/sqlite/deathwatch.db | Bin 36864 -> 36864 bytes database/sqlite/deathwatch.db-shm | Bin 32768 -> 32768 bytes database/sqlite/deathwatch.db-wal | Bin 94792 -> 420272 bytes jest.config.js | 6 + package-lock.json | 333 +++++++++++++++++++- package.json | 3 +- src/components/PlayerTab.jsx | 93 ++++-- tests/gmtest.test.js | 77 +++++ tests/playertest.test.js | 67 ++++ 13 files changed, 1102 insertions(+), 116 deletions(-) create mode 100644 tests/gmtest.test.js create mode 100644 tests/playertest.test.js diff --git a/database/backend.log b/database/backend.log index 2cbb59e..9746d19 100644 --- a/database/backend.log +++ b/database/backend.log @@ -180,3 +180,406 @@ Connected to MongoDB [2025-08-15T09:10:12.600Z] SESSION: Validate success 6cc910f09effb8c44ece418cc44b69fcb17501b7070656b4e1ba0cf95fff4844 christoffer [2025-08-15T09:11:44.183Z] SESSION: Validate success 6cc910f09effb8c44ece418cc44b69fcb17501b7070656b4e1ba0cf95fff4844 christoffer [2025-08-15T09:12:00.469Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:28.458Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:34.070Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:34.678Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:44.307Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:45.126Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:46.271Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:47.519Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:47.859Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:48.030Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:48.590Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:53.231Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:53.736Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:55.926Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:57.034Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:33:57.400Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:34:00.002Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:34:20.089Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:34:37.318Z] SESSION: Login success christoffer 5925e8bcc9d607a27648a80405d9985b1b3e7353b82d638b2b5b4ffe36c23449 +[2025-08-15T09:34:37.330Z] SESSION: Validate success 5925e8bcc9d607a27648a80405d9985b1b3e7353b82d638b2b5b4ffe36c23449 christoffer +[2025-08-15T09:34:37.333Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:36:42.121Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:37:03.496Z] SESSION: Valid 5925e8bcc9d607a27648a80405d9985b1b3e7353b82d638b2b5b4ffe36c23449 christoffer PUT /api/players/christoffer +[2025-08-15T09:37:07.117Z] API: Failed to update player christoffer {"code":"MODULE_NOT_FOUND","requireStack":["/home/alex/git/dwroller/database/routes/playerRoutes-sqlite.js","/home/alex/git/dwroller/database/server.js"]} +[2025-08-15T09:37:10.595Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:37:18.118Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:37:24.505Z] SESSION: Logout success 5925e8bcc9d607a27648a80405d9985b1b3e7353b82d638b2b5b4ffe36c23449 +[2025-08-15T09:37:24.513Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:37:27.500Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:37:27.911Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:37:28.085Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:37:30.703Z] SESSION: Login success phillip 0b28f0a3aa3398d909ba4e44bdff7329529908b8ec88ad21fe003ce59caa044c +[2025-08-15T09:37:30.714Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:37:30.717Z] SESSION: Validate success 0b28f0a3aa3398d909ba4e44bdff7329529908b8ec88ad21fe003ce59caa044c phillip +[2025-08-15T09:39:02.869Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:39:04.872Z] SESSION: Login success christoffer b19e94a3b117740e41cabd7803515d42023d41e286d3303c710f591e9f70c8e5 +[2025-08-15T09:39:04.907Z] SESSION: Validate success b19e94a3b117740e41cabd7803515d42023d41e286d3303c710f591e9f70c8e5 christoffer +[2025-08-15T09:39:12.226Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:39:14.895Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:39:34.142Z] SESSION: Logout success b19e94a3b117740e41cabd7803515d42023d41e286d3303c710f591e9f70c8e5 +[2025-08-15T09:39:34.192Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:42:54.757Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:42:54.772Z] API: Failed to create player gmtest {"code":"MODULE_NOT_FOUND","requireStack":["/home/alex/git/dwroller/database/routes/playerRoutes-sqlite.js","/home/alex/git/dwroller/database/server.js"]} +[2025-08-15T09:42:54.785Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:42:54.799Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T09:42:54.812Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:42:54.826Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T09:42:54.839Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T09:42:54.852Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:43:18.701Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:45:20.371Z] API: Failed to create player gmtest {"code":"MODULE_NOT_FOUND","requireStack":["/home/alex/git/dwroller/database/routes/playerRoutes-sqlite.js","/home/alex/git/dwroller/database/server.js"]} +[2025-08-15T09:47:04.187Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:47:04.198Z] API: Failed to create player gmtest {"code":"MODULE_NOT_FOUND","requireStack":["/home/alex/git/dwroller/database/routes/playerRoutes-sqlite.js","/home/alex/git/dwroller/database/server.js"]} +[2025-08-15T09:47:04.208Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:47:04.219Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T09:47:04.230Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T09:47:04.240Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T09:47:04.250Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:47:44.631Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:47:47.404Z] SESSION: Login success phillip 2d60199a5a383cf6c1467bd42b33055ad0ce77c4935bac31a6fd4d812c535f0a +[2025-08-15T09:47:47.475Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:47:47.482Z] SESSION: Validate success 2d60199a5a383cf6c1467bd42b33055ad0ce77c4935bac31a6fd4d812c535f0a phillip +[2025-08-15T09:47:50.107Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:48:03.443Z] API: Failed to create player gmtest2 {"code":"MODULE_NOT_FOUND","requireStack":["/home/alex/git/dwroller/database/routes/playerRoutes-sqlite.js","/home/alex/git/dwroller/database/server.js"]} +[2025-08-15T09:48:14.288Z] API: Failed to create player gmtest2 {"code":"MODULE_NOT_FOUND","requireStack":["/home/alex/git/dwroller/database/routes/playerRoutes-sqlite.js","/home/alex/git/dwroller/database/server.js"]} +[2025-08-15T09:49:19.949Z] DB: getPlayerByName - start gmtest +[2025-08-15T09:49:19.950Z] DB: getPlayerByName - not found gmtest +[2025-08-15T09:49:20.115Z] DB: createPlayer - start gmtest +[2025-08-15T09:49:20.125Z] DB: createPlayer - done gmtest rowid 4 +[2025-08-15T09:49:20.125Z] API: Created player gmtest +[2025-08-15T09:52:14.622Z] DB: getAllPlayers - start +[2025-08-15T09:52:14.622Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T09:52:14.622Z] API: Fetch all players (public) Found 4 players +[2025-08-15T09:52:19.681Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T09:52:19.682Z] DB: getPlayerByName - start gmtest +[2025-08-15T09:52:19.682Z] DB: getPlayerByName - found gmtest +[2025-08-15T09:52:19.682Z] DB: updatePlayer - start gmtest +[2025-08-15T09:52:19.683Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T09:52:19.683Z] DB: getPlayerByName - start gmtest +[2025-08-15T09:52:19.683Z] DB: getPlayerByName - found gmtest +[2025-08-15T09:52:19.683Z] API: Updated player gmtest +[2025-08-15T09:52:25.522Z] DB: getPlayerByName - start gmtest +[2025-08-15T09:52:25.523Z] DB: getPlayerByName - found gmtest +[2025-08-15T09:52:25.523Z] API: Fetch player gmtest +[2025-08-15T09:52:31.755Z] DB: getPlayerByName - start gmtest +[2025-08-15T09:52:31.755Z] DB: getPlayerByName - found gmtest +[2025-08-15T09:52:31.756Z] API: Fetch player gmtest +[2025-08-15T09:52:38.318Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T09:52:38.318Z] DB: getPlayerByName - start gmtest +[2025-08-15T09:52:38.318Z] DB: getPlayerByName - found gmtest +[2025-08-15T09:52:38.398Z] DB: updatePlayer - start gmtest +[2025-08-15T09:52:38.398Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T09:52:38.398Z] DB: getPlayerByName - start gmtest +[2025-08-15T09:52:38.398Z] DB: getPlayerByName - found gmtest +[2025-08-15T09:52:38.398Z] API: Updated player gmtest +[2025-08-15T09:52:43.073Z] DB: getPlayerByName - start gmtest +[2025-08-15T09:52:43.074Z] DB: getPlayerByName - found gmtest +[2025-08-15T09:52:43.179Z] DB: createSession - start 959f0b154d1a554483362280c79ce52f0584358dc8a95228125b9be555b2cf89 expiresAt 2025-08-16T09:52:43.179Z +[2025-08-15T09:52:43.180Z] DB: createSession - done 959f0b154d1a554483362280c79ce52f0584358dc8a95228125b9be555b2cf89 +[2025-08-15T09:52:43.180Z] API: Player login gmtest 959f0b154d1a554483362280c79ce52f0584358dc8a95228125b9be555b2cf89 +[2025-08-15T09:52:47.647Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T09:52:47.647Z] DB: getPlayerByName - start gmtest +[2025-08-15T09:52:47.647Z] DB: getPlayerByName - found gmtest +[2025-08-15T09:52:47.647Z] DB: updatePlayer - start gmtest +[2025-08-15T09:52:47.647Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T09:52:47.647Z] DB: getPlayerByName - start gmtest +[2025-08-15T09:52:47.647Z] DB: getPlayerByName - found gmtest +[2025-08-15T09:52:47.647Z] API: Updated player gmtest +[2025-08-15T09:52:52.260Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T09:52:52.260Z] DB: deletePlayer - start gmtest +[2025-08-15T09:52:52.261Z] DB: deletePlayer - changes 1 gmtest +[2025-08-15T09:52:52.261Z] API: Deleted player gmtest +[2025-08-15T09:52:57.546Z] DB: getAllPlayers - start +[2025-08-15T09:52:57.546Z] DB: getAllPlayers - resultCount 3 +[2025-08-15T09:52:57.546Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:54:44.664Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:54:44.664Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:54:49.647Z] DB: getAllPlayers - start +[2025-08-15T09:54:49.647Z] DB: getAllPlayers - resultCount 3 +[2025-08-15T09:54:49.647Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:55:47.472Z] SESSION: GM bypass accepted PUT /api/players/christoffer +[2025-08-15T09:55:47.473Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:55:47.473Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:55:47.589Z] DB: updatePlayer - start christoffer +[2025-08-15T09:55:47.590Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T09:55:47.590Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:55:47.590Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:55:47.590Z] API: Updated player christoffer +[2025-08-15T09:55:52.341Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:55:52.341Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:55:52.420Z] DB: createSession - start b624e0e4569e376c8d408bebb9a4d02ab67c06c49d6374b78506b2e29335ac49 expiresAt 2025-08-16T09:55:52.420Z +[2025-08-15T09:55:52.421Z] DB: createSession - done b624e0e4569e376c8d408bebb9a4d02ab67c06c49d6374b78506b2e29335ac49 +[2025-08-15T09:55:52.421Z] API: Player login christoffer b624e0e4569e376c8d408bebb9a4d02ab67c06c49d6374b78506b2e29335ac49 +[2025-08-15T09:56:00.491Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:56:00.491Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:56:00.491Z] API: Fetch player christoffer +[2025-08-15T09:56:05.962Z] DB: getSession - start b624e0e4569e376c8d408bebb9a4d02ab67c06c49d6374b78506b2e29335ac49 +[2025-08-15T09:56:05.963Z] DB: getSession - ok b624e0e4569e376c8d408bebb9a4d02ab67c06c49d6374b78506b2e29335ac49 +[2025-08-15T09:56:05.963Z] SESSION: Valid b624e0e4569e376c8d408bebb9a4d02ab67c06c49d6374b78506b2e29335ac49 christoffer PUT /api/players/christoffer +[2025-08-15T09:56:05.964Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:56:05.964Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:56:05.964Z] DB: updatePlayer - start christoffer +[2025-08-15T09:56:05.964Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T09:56:05.964Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:56:05.964Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:56:05.964Z] API: Updated player christoffer +[2025-08-15T09:56:10.814Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:56:10.814Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:56:10.814Z] API: Fetch player christoffer +[2025-08-15T09:56:28.669Z] DB: getSession - start b624e0e4569e376c8d408bebb9a4d02ab67c06c49d6374b78506b2e29335ac49 +[2025-08-15T09:56:28.670Z] DB: getSession - ok b624e0e4569e376c8d408bebb9a4d02ab67c06c49d6374b78506b2e29335ac49 +[2025-08-15T09:56:28.670Z] SESSION: Valid b624e0e4569e376c8d408bebb9a4d02ab67c06c49d6374b78506b2e29335ac49 christoffer PUT /api/players/christoffer +[2025-08-15T09:56:28.670Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:56:28.670Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:56:28.670Z] DB: updatePlayer - start christoffer +[2025-08-15T09:56:28.670Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T09:56:28.670Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:56:28.671Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:56:28.671Z] API: Updated player christoffer +[2025-08-15T09:56:35.927Z] DB: deleteSession - start 0b28f0a3aa3398d909ba4e44bdff7329529908b8ec88ad21fe003ce59caa044c +[2025-08-15T09:56:35.927Z] DB: deleteSession - changes 1 0b28f0a3aa3398d909ba4e44bdff7329529908b8ec88ad21fe003ce59caa044c +[2025-08-15T09:56:35.927Z] SESSION: Logout success 0b28f0a3aa3398d909ba4e44bdff7329529908b8ec88ad21fe003ce59caa044c +[2025-08-15T09:56:35.937Z] DB: getAllPlayers - start +[2025-08-15T09:56:35.937Z] DB: getAllPlayers - resultCount 3 +[2025-08-15T09:56:35.937Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:56:38.419Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:56:38.420Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:56:38.420Z] DB: createSession - start df93fb133392f56772c27359ce103dacab71dbf72c91b787efe3ce620b00d8e3 expiresAt 2025-08-16T09:56:38.420Z +[2025-08-15T09:56:38.420Z] DB: createSession - done df93fb133392f56772c27359ce103dacab71dbf72c91b787efe3ce620b00d8e3 +[2025-08-15T09:56:38.420Z] SESSION: Login success christoffer df93fb133392f56772c27359ce103dacab71dbf72c91b787efe3ce620b00d8e3 +[2025-08-15T09:56:38.428Z] DB: getAllPlayers - start +[2025-08-15T09:56:38.429Z] DB: getAllPlayers - resultCount 3 +[2025-08-15T09:56:38.429Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:56:38.438Z] DB: getSession - start df93fb133392f56772c27359ce103dacab71dbf72c91b787efe3ce620b00d8e3 +[2025-08-15T09:56:38.438Z] DB: getSession - ok df93fb133392f56772c27359ce103dacab71dbf72c91b787efe3ce620b00d8e3 +[2025-08-15T09:56:38.438Z] SESSION: Validate success df93fb133392f56772c27359ce103dacab71dbf72c91b787efe3ce620b00d8e3 christoffer +[2025-08-15T09:56:45.032Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:56:45.032Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:56:45.032Z] API: Fetch player christoffer +[2025-08-15T09:56:48.749Z] DB: getSession - start df93fb133392f56772c27359ce103dacab71dbf72c91b787efe3ce620b00d8e3 +[2025-08-15T09:56:48.749Z] DB: getSession - ok df93fb133392f56772c27359ce103dacab71dbf72c91b787efe3ce620b00d8e3 +[2025-08-15T09:56:48.749Z] SESSION: Validate success df93fb133392f56772c27359ce103dacab71dbf72c91b787efe3ce620b00d8e3 christoffer +[2025-08-15T09:56:50.169Z] DB: getAllPlayers - start +[2025-08-15T09:56:50.169Z] DB: getAllPlayers - resultCount 3 +[2025-08-15T09:56:50.169Z] API: Fetch all players (public) Found 3 players +[2025-08-15T09:58:59.451Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:58:59.451Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:58:59.532Z] DB: createSession - start 2db67a36a9c4453f5d99c9a6b20a49f892f79f4e36575c139fe596ad256ad7ff expiresAt 2025-08-16T09:58:59.532Z +[2025-08-15T09:58:59.532Z] DB: createSession - done 2db67a36a9c4453f5d99c9a6b20a49f892f79f4e36575c139fe596ad256ad7ff +[2025-08-15T09:58:59.532Z] API: Player login christoffer 2db67a36a9c4453f5d99c9a6b20a49f892f79f4e36575c139fe596ad256ad7ff +[2025-08-15T09:59:28.107Z] DB: getSession - start 2db67a36a9c4453f5d99c9a6b20a49f892f79f4e36575c139fe596ad256ad7ff +[2025-08-15T09:59:28.107Z] DB: getSession - ok 2db67a36a9c4453f5d99c9a6b20a49f892f79f4e36575c139fe596ad256ad7ff +[2025-08-15T09:59:28.107Z] SESSION: Valid 2db67a36a9c4453f5d99c9a6b20a49f892f79f4e36575c139fe596ad256ad7ff christoffer PUT /api/players/christoffer +[2025-08-15T09:59:28.108Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:59:28.108Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:59:28.108Z] DB: updatePlayer - start christoffer +[2025-08-15T09:59:28.108Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T09:59:28.108Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:59:28.108Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:59:28.108Z] API: Updated player christoffer +[2025-08-15T09:59:34.647Z] DB: getPlayerByName - start christoffer +[2025-08-15T09:59:34.647Z] DB: getPlayerByName - found christoffer +[2025-08-15T09:59:34.647Z] API: Fetch player christoffer +[2025-08-15T10:02:29.124Z] DB: getAllPlayers - start +[2025-08-15T10:02:29.126Z] DB: getAllPlayers - resultCount 3 +[2025-08-15T10:02:36.492Z] DB: getAllPlayers - start +[2025-08-15T10:02:36.493Z] DB: getAllPlayers - resultCount 3 +[2025-08-15T10:04:04.343Z] DB: getAllPlayers - start +[2025-08-15T10:04:04.344Z] DB: getAllPlayers - resultCount 3 +[2025-08-15T10:07:18.463Z] DB: getAllPlayers - start +[2025-08-15T10:07:18.464Z] DB: getAllPlayers - resultCount 3 +[2025-08-15T10:12:41.561Z] DB: getAllPlayers - start +[2025-08-15T10:12:41.563Z] DB: getAllPlayers - resultCount 3 +[2025-08-15T10:13:17.458Z] DB: getAllPlayers - start +[2025-08-15T10:13:17.459Z] DB: getAllPlayers - resultCount 3 +[2025-08-15T10:13:55.439Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:13:55.440Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:13:55.522Z] DB: createSession - start 89217200ddee8c2dfbc6c3aa9c6d29087fd30ce045bbae12340d367cc89e20bd expiresAt 2025-08-16T10:13:55.522Z +[2025-08-15T10:13:55.522Z] DB: createSession - done 89217200ddee8c2dfbc6c3aa9c6d29087fd30ce045bbae12340d367cc89e20bd +[2025-08-15T10:13:55.522Z] API: Player login christoffer 89217200ddee8c2dfbc6c3aa9c6d29087fd30ce045bbae12340d367cc89e20bd +[2025-08-15T10:13:55.536Z] DB: getSession - start 89217200ddee8c2dfbc6c3aa9c6d29087fd30ce045bbae12340d367cc89e20bd +[2025-08-15T10:13:55.536Z] DB: getSession - ok 89217200ddee8c2dfbc6c3aa9c6d29087fd30ce045bbae12340d367cc89e20bd +[2025-08-15T10:13:55.536Z] SESSION: Valid 89217200ddee8c2dfbc6c3aa9c6d29087fd30ce045bbae12340d367cc89e20bd christoffer PUT /api/players/christoffer +[2025-08-15T10:13:55.536Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:13:55.537Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:13:55.537Z] DB: updatePlayer - start christoffer +[2025-08-15T10:13:55.537Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T10:13:55.537Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:13:55.537Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:13:55.537Z] API: Updated player christoffer +[2025-08-15T10:13:55.544Z] DB: getSession - start 89217200ddee8c2dfbc6c3aa9c6d29087fd30ce045bbae12340d367cc89e20bd +[2025-08-15T10:13:55.544Z] DB: getSession - ok 89217200ddee8c2dfbc6c3aa9c6d29087fd30ce045bbae12340d367cc89e20bd +[2025-08-15T10:13:55.544Z] SESSION: Valid 89217200ddee8c2dfbc6c3aa9c6d29087fd30ce045bbae12340d367cc89e20bd christoffer PUT /api/players/christoffer +[2025-08-15T10:13:55.544Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:13:55.544Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:13:55.544Z] DB: updatePlayer - start christoffer +[2025-08-15T10:13:55.544Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T10:13:55.545Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:13:55.545Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:13:55.545Z] API: Updated player christoffer +[2025-08-15T10:13:56.163Z] DB: getAllPlayers - start +[2025-08-15T10:13:56.164Z] DB: getAllPlayers - resultCount 3 +[2025-08-15T10:14:23.849Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:14:23.849Z] DB: getPlayerByName - not found gmtest +[2025-08-15T10:14:23.929Z] DB: createPlayer - start gmtest +[2025-08-15T10:14:23.929Z] DB: createPlayer - done gmtest rowid 5 +[2025-08-15T10:14:23.935Z] API: Created player gmtest +[2025-08-15T10:14:23.945Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T10:14:23.945Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:14:23.945Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:14:23.945Z] DB: updatePlayer - start gmtest +[2025-08-15T10:14:23.945Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T10:14:23.945Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:14:23.945Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:14:23.946Z] API: Updated player gmtest +[2025-08-15T10:14:23.950Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T10:14:23.950Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:14:23.950Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:14:24.030Z] DB: updatePlayer - start gmtest +[2025-08-15T10:14:24.030Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T10:14:24.030Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:14:24.030Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:14:24.030Z] API: Updated player gmtest +[2025-08-15T10:14:24.067Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T10:14:24.067Z] DB: deletePlayer - start gmtest +[2025-08-15T10:14:24.067Z] DB: deletePlayer - changes 1 gmtest +[2025-08-15T10:14:24.067Z] API: Deleted player gmtest +[2025-08-15T10:14:24.321Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:14:24.321Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:14:24.400Z] DB: createSession - start bdf9dddc06ea9d1830f1c689cf2c8b81e6b79cfbd81bfb5b84bc46a9e24ae78f expiresAt 2025-08-16T10:14:24.400Z +[2025-08-15T10:14:24.401Z] DB: createSession - done bdf9dddc06ea9d1830f1c689cf2c8b81e6b79cfbd81bfb5b84bc46a9e24ae78f +[2025-08-15T10:14:24.401Z] API: Player login christoffer bdf9dddc06ea9d1830f1c689cf2c8b81e6b79cfbd81bfb5b84bc46a9e24ae78f +[2025-08-15T10:14:24.410Z] DB: getSession - start bdf9dddc06ea9d1830f1c689cf2c8b81e6b79cfbd81bfb5b84bc46a9e24ae78f +[2025-08-15T10:14:24.410Z] DB: getSession - ok bdf9dddc06ea9d1830f1c689cf2c8b81e6b79cfbd81bfb5b84bc46a9e24ae78f +[2025-08-15T10:14:24.410Z] SESSION: Valid bdf9dddc06ea9d1830f1c689cf2c8b81e6b79cfbd81bfb5b84bc46a9e24ae78f christoffer PUT /api/players/christoffer +[2025-08-15T10:14:24.410Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:14:24.410Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:14:24.410Z] DB: updatePlayer - start christoffer +[2025-08-15T10:14:24.411Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T10:14:24.411Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:14:24.411Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:14:24.411Z] API: Updated player christoffer +[2025-08-15T10:14:24.417Z] DB: getSession - start bdf9dddc06ea9d1830f1c689cf2c8b81e6b79cfbd81bfb5b84bc46a9e24ae78f +[2025-08-15T10:14:24.417Z] DB: getSession - ok bdf9dddc06ea9d1830f1c689cf2c8b81e6b79cfbd81bfb5b84bc46a9e24ae78f +[2025-08-15T10:14:24.417Z] SESSION: Valid bdf9dddc06ea9d1830f1c689cf2c8b81e6b79cfbd81bfb5b84bc46a9e24ae78f christoffer PUT /api/players/christoffer +[2025-08-15T10:14:24.417Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:14:24.417Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:14:24.417Z] DB: updatePlayer - start christoffer +[2025-08-15T10:14:24.418Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T10:14:24.418Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:14:24.418Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:14:24.418Z] API: Updated player christoffer +[2025-08-15T10:14:24.778Z] DB: getAllPlayers - start +[2025-08-15T10:14:24.778Z] DB: getAllPlayers - resultCount 3 +[2025-08-15T10:14:57.192Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:14:57.192Z] DB: getPlayerByName - not found gmtest +[2025-08-15T10:14:57.272Z] DB: createPlayer - start gmtest +[2025-08-15T10:14:57.272Z] DB: createPlayer - done gmtest rowid 6 +[2025-08-15T10:14:57.272Z] API: Created player gmtest +[2025-08-15T10:14:57.292Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T10:14:57.293Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:14:57.293Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:14:57.293Z] DB: updatePlayer - start gmtest +[2025-08-15T10:14:57.293Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T10:14:57.293Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:14:57.293Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:14:57.294Z] API: Updated player gmtest +[2025-08-15T10:14:57.313Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T10:14:57.313Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:14:57.313Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:14:57.393Z] DB: updatePlayer - start gmtest +[2025-08-15T10:14:57.393Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T10:14:57.393Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:14:57.394Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:14:57.394Z] API: Updated player gmtest +[2025-08-15T10:14:57.412Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:14:57.412Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:14:57.492Z] DB: createSession - start ed409e978652642d30d86d34aa1cc727d6ab0493bf562e936f067d8ae82918da expiresAt 2025-08-16T10:14:57.492Z +[2025-08-15T10:14:57.493Z] DB: createSession - done ed409e978652642d30d86d34aa1cc727d6ab0493bf562e936f067d8ae82918da +[2025-08-15T10:14:57.493Z] API: Player login gmtest ed409e978652642d30d86d34aa1cc727d6ab0493bf562e936f067d8ae82918da +[2025-08-15T10:14:57.513Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T10:14:57.514Z] DB: deletePlayer - start gmtest +[2025-08-15T10:14:57.514Z] DB: deletePlayer - changes 1 gmtest +[2025-08-15T10:14:57.514Z] API: Deleted player gmtest +[2025-08-15T10:15:03.883Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:15:03.884Z] DB: getPlayerByName - not found gmtest +[2025-08-15T10:16:37.465Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:16:37.465Z] DB: getPlayerByName - not found gmtest +[2025-08-15T10:16:37.544Z] DB: createPlayer - start gmtest +[2025-08-15T10:16:37.545Z] DB: createPlayer - done gmtest rowid 7 +[2025-08-15T10:16:37.545Z] API: Created player gmtest +[2025-08-15T10:16:37.555Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T10:16:37.555Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:16:37.555Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:16:37.555Z] DB: updatePlayer - start gmtest +[2025-08-15T10:16:37.555Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T10:16:37.555Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:16:37.555Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:16:37.555Z] API: Updated player gmtest +[2025-08-15T10:16:37.559Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T10:16:37.560Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:16:37.560Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:16:37.639Z] DB: updatePlayer - start gmtest +[2025-08-15T10:16:37.640Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T10:16:37.640Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:16:37.640Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:16:37.640Z] API: Updated player gmtest +[2025-08-15T10:16:37.678Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T10:16:37.678Z] DB: deletePlayer - start gmtest +[2025-08-15T10:16:37.678Z] DB: deletePlayer - changes 1 gmtest +[2025-08-15T10:16:37.678Z] API: Deleted player gmtest +[2025-08-15T10:16:38.071Z] DB: getAllPlayers - start +[2025-08-15T10:16:38.071Z] DB: getAllPlayers - resultCount 3 +[2025-08-15T10:16:38.331Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:16:38.331Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:16:38.411Z] DB: createSession - start a9d8e32089760c947f479f6a4e5f9cf57250a3741b1cc633c3e98db23c1386ea expiresAt 2025-08-16T10:16:38.411Z +[2025-08-15T10:16:38.411Z] DB: createSession - done a9d8e32089760c947f479f6a4e5f9cf57250a3741b1cc633c3e98db23c1386ea +[2025-08-15T10:16:38.411Z] API: Player login christoffer a9d8e32089760c947f479f6a4e5f9cf57250a3741b1cc633c3e98db23c1386ea +[2025-08-15T10:16:38.421Z] DB: getSession - start a9d8e32089760c947f479f6a4e5f9cf57250a3741b1cc633c3e98db23c1386ea +[2025-08-15T10:16:38.421Z] DB: getSession - ok a9d8e32089760c947f479f6a4e5f9cf57250a3741b1cc633c3e98db23c1386ea +[2025-08-15T10:16:38.421Z] SESSION: Valid a9d8e32089760c947f479f6a4e5f9cf57250a3741b1cc633c3e98db23c1386ea christoffer PUT /api/players/christoffer +[2025-08-15T10:16:38.421Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:16:38.421Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:16:38.421Z] DB: updatePlayer - start christoffer +[2025-08-15T10:16:38.421Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T10:16:38.421Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:16:38.422Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:16:38.422Z] API: Updated player christoffer +[2025-08-15T10:16:38.428Z] DB: getSession - start a9d8e32089760c947f479f6a4e5f9cf57250a3741b1cc633c3e98db23c1386ea +[2025-08-15T10:16:38.428Z] DB: getSession - ok a9d8e32089760c947f479f6a4e5f9cf57250a3741b1cc633c3e98db23c1386ea +[2025-08-15T10:16:38.428Z] SESSION: Valid a9d8e32089760c947f479f6a4e5f9cf57250a3741b1cc633c3e98db23c1386ea christoffer PUT /api/players/christoffer +[2025-08-15T10:16:38.429Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:16:38.429Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:16:38.429Z] DB: updatePlayer - start christoffer +[2025-08-15T10:16:38.429Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T10:16:38.429Z] DB: getPlayerByName - start christoffer +[2025-08-15T10:16:38.429Z] DB: getPlayerByName - found christoffer +[2025-08-15T10:16:38.429Z] API: Updated player christoffer +[2025-08-15T10:17:17.822Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:17:17.823Z] DB: getPlayerByName - not found gmtest +[2025-08-15T10:17:17.902Z] DB: createPlayer - start gmtest +[2025-08-15T10:17:17.903Z] DB: createPlayer - done gmtest rowid 8 +[2025-08-15T10:17:17.903Z] API: Created player gmtest +[2025-08-15T10:17:17.914Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T10:17:17.915Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:17:17.915Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:17:17.915Z] DB: updatePlayer - start gmtest +[2025-08-15T10:17:17.915Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T10:17:17.915Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:17:17.915Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:17:17.915Z] API: Updated player gmtest +[2025-08-15T10:17:17.925Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T10:17:17.925Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:17:17.926Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:17:18.005Z] DB: updatePlayer - start gmtest +[2025-08-15T10:17:18.005Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T10:17:18.005Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:17:18.006Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:17:18.006Z] API: Updated player gmtest +[2025-08-15T10:17:18.017Z] DB: getPlayerByName - start gmtest +[2025-08-15T10:17:18.018Z] DB: getPlayerByName - found gmtest +[2025-08-15T10:17:18.099Z] DB: createSession - start e5a905f54488e26aae838d7a16d7f50d681f54c72749485f7944464e35b92e9d expiresAt 2025-08-16T10:17:18.099Z +[2025-08-15T10:17:18.100Z] DB: createSession - done e5a905f54488e26aae838d7a16d7f50d681f54c72749485f7944464e35b92e9d +[2025-08-15T10:17:18.100Z] API: Player login gmtest e5a905f54488e26aae838d7a16d7f50d681f54c72749485f7944464e35b92e9d diff --git a/database/requireSession.js b/database/requireSession.js index 7d34dea..40f3853 100644 --- a/database/requireSession.js +++ b/database/requireSession.js @@ -1,4 +1,4 @@ -const Session = require('./sessionModel'); +const { sessionHelpers } = require('./sqlite-db'); const fs = require('fs'); const path = require('path'); function logToFile(...args) { @@ -7,26 +7,44 @@ function logToFile(...args) { } // Express middleware to require a valid sessionId in req.headers['x-session-id'] or req.body.sessionId -// Attaches req.session and req.sessionPlayerName if valid, else sends 401 +// Accepts a GM bypass header 'x-gm-secret' matching process.env.GM_PASSWORD or 'bongo' for local convenience module.exports = async function requireSession(req, res, next) { - // Ensure req.body is always an object - if (typeof req.body !== 'object' || req.body === null) req.body = {}; - const sessionId = req.headers['x-session-id'] || req.body.sessionId || req.query.sessionId; - if (!sessionId) { - logToFile('SESSION: Missing sessionId', req.method, req.originalUrl); - return res.status(401).json({ error: 'Session required' }); + try { + // GM bypass + const gmSecret = req.headers['x-gm-secret'] || req.query.gmSecret || req.body.gmSecret; + const gmPassword = process.env.GM_PASSWORD || 'bongo'; + if (gmSecret && String(gmSecret) === String(gmPassword)) { + logToFile('SESSION: GM bypass accepted', req.method, req.originalUrl); + req.session = { data: { playerName: 'GM' }, playerName: 'GM' }; + req.sessionPlayerName = 'GM'; + return next(); + } + + // Ensure req.body is always an object + if (typeof req.body !== 'object' || req.body === null) req.body = {}; + const sessionId = req.headers['x-session-id'] || req.body.sessionId || req.query.sessionId; + if (!sessionId) { + logToFile('SESSION: Missing sessionId', req.method, req.originalUrl); + return res.status(401).json({ error: 'Session required' }); + } + + const session = sessionHelpers.get(sessionId); + if (!session) { + logToFile('SESSION: Not found', sessionId, req.method, req.originalUrl); + return res.status(401).json({ error: 'Invalid or expired session' }); + } + + if (new Date(session.expiresAt) < new Date()) { + logToFile('SESSION: Expired', sessionId, req.method, req.originalUrl); + return res.status(401).json({ error: 'Invalid or expired session' }); + } + + logToFile('SESSION: Valid', sessionId, session.data.playerName, req.method, req.originalUrl); + req.session = session; + req.sessionPlayerName = session.data.playerName; + next(); + } catch (err) { + logToFile('SESSION: Error', err && err.message ? err.message : String(err)); + return res.status(500).json({ error: 'Session validation failed' }); } - const session = await Session.findOne({ sessionId }); - if (!session) { - logToFile('SESSION: Not found', sessionId, req.method, req.originalUrl); - return res.status(401).json({ error: 'Invalid or expired session' }); - } - if (session.expiresAt < new Date()) { - logToFile('SESSION: Expired', sessionId, req.method, req.originalUrl); - return res.status(401).json({ error: 'Invalid or expired session' }); - } - logToFile('SESSION: Valid', sessionId, session.playerName, req.method, req.originalUrl); - req.session = session; - req.sessionPlayerName = session.playerName; - next(); }; diff --git a/database/routes/playerRoutes-sqlite.js b/database/routes/playerRoutes-sqlite.js index 72743d4..9fcd7b0 100644 --- a/database/routes/playerRoutes-sqlite.js +++ b/database/routes/playerRoutes-sqlite.js @@ -1,5 +1,5 @@ const express = require('express'); -const { playerHelpers } = require('../sqlite-db'); +const { playerHelpers, sessionHelpers } = require('../sqlite-db'); const fs = require('fs'); const path = require('path'); @@ -12,6 +12,30 @@ function logToFile(...args) { const requireSession = require('../requireSession'); const router = express.Router(); +// Add safe bcrypt helpers to avoid MODULE_NOT_FOUND failures at runtime +async function safeHash(pw) { + if (!pw) return ''; + try { + const bcrypt = require('bcrypt'); + return await bcrypt.hash(pw, 10); + } catch (err) { + // Fallback to storing plaintext (development only) and log the error + logToFile('WARN: bcrypt.hash failed, falling back to plaintext pw', err && err.stack ? err.stack : String(err)); + return String(pw); + } +} + +async function safeCompare(candidate, hashed) { + try { + const bcrypt = require('bcrypt'); + return await bcrypt.compare(candidate, hashed); + } catch (err) { + // If bcrypt not available, fall back to plaintext comparison (dev only) + logToFile('WARN: bcrypt.compare failed, falling back to plaintext compare', err && err.stack ? err.stack : String(err)); + return String(candidate) === String(hashed); + } +} + // TEMP ADMIN: List all users router.get('/admin/list', async (req, res) => { try { @@ -48,11 +72,17 @@ router.get('/', async (req, res) => { try { const players = playerHelpers.getAll(); logToFile('API: Fetch all players (public)', `Found ${players.length} players`); - - // Only send name for dropdown if not authed - if (!req.headers['x-session-id']) { + + // Allow full list if client provided a valid session id OR the GM secret header + const gmSecret = req.headers['x-gm-secret'] || req.query.gmSecret || (req.body && req.body.gmSecret); + const gmPassword = process.env.GM_PASSWORD || 'bongo'; + const isGm = gmSecret && String(gmSecret) === String(gmPassword); + const isAuthed = !!req.headers['x-session-id']; + + if (!isAuthed && !isGm) { return res.json(players.map(p => ({ name: p.name }))); } + res.json(players); } catch (error) { logToFile('API: Failed to fetch players', error); @@ -90,11 +120,10 @@ router.post('/', async (req, res) => { return res.status(409).json({ error: 'Player already exists' }); } - // Create password hash if password provided + // Create password hash if password provided (use safeHash) let pwHash = ''; if (pw) { - const bcrypt = require('bcrypt'); - pwHash = await bcrypt.hash(pw, 10); + pwHash = await safeHash(pw); } const newPlayer = playerHelpers.create({ @@ -109,7 +138,8 @@ router.post('/', async (req, res) => { logToFile('API: Created player', name); res.status(201).json(newPlayer); } catch (error) { - logToFile('API: Failed to create player', req.body.name, error); + // Log stack for easier debugging + logToFile('API: Failed to create player', req.body && req.body.name, error && error.stack ? error.stack : error); res.status(500).json({ error: 'Failed to create player' }); } }); @@ -126,10 +156,9 @@ router.put('/:name', requireSession, async (req, res) => { return res.status(404).json({ error: 'Player not found' }); } - // Handle password update if provided + // Handle password update if provided using safeHash if (updateData.pw) { - const bcrypt = require('bcrypt'); - updateData.pwHash = await bcrypt.hash(updateData.pw, 10); + updateData.pwHash = await safeHash(updateData.pw); } // Merge the data properly @@ -141,38 +170,20 @@ router.put('/:name', requireSession, async (req, res) => { pwHash: updateData.pwHash || existingPlayer.pwHash }; - // Handle the special case where frontend sends flat data that should go into tabInfo - if (updateData.playerName || updateData.charName || updateData.characteristics) { - mergedData.tabInfo = { - ...mergedData.tabInfo, - playerName: updateData.playerName || mergedData.tabInfo.playerName, - charName: updateData.charName || mergedData.tabInfo.charName, - gear: updateData.gear || mergedData.tabInfo.gear, - chapter: updateData.chapter || mergedData.tabInfo.chapter, - demeanour: updateData.demeanour || mergedData.tabInfo.demeanour, - speciality: updateData.speciality || mergedData.tabInfo.speciality, - rank: updateData.rank || mergedData.tabInfo.rank, - powerArmour: updateData.powerArmour || mergedData.tabInfo.powerArmour, - description: updateData.description || mergedData.tabInfo.description, - pastEvent: updateData.pastEvent || mergedData.tabInfo.pastEvent, - personalDemeanour: updateData.personalDemeanour || mergedData.tabInfo.personalDemeanour, - characteristics: updateData.characteristics || mergedData.tabInfo.characteristics, - skills: updateData.skills || mergedData.tabInfo.skills, - weapons: updateData.weapons || mergedData.tabInfo.weapons, - armour: updateData.armour || mergedData.tabInfo.armour, - talents: updateData.talents || mergedData.tabInfo.talents, - psychic: updateData.psychic || mergedData.tabInfo.psychic, - wounds: updateData.wounds || mergedData.tabInfo.wounds, - insanity: updateData.insanity || mergedData.tabInfo.insanity, - movement: updateData.movement || mergedData.tabInfo.movement, - fate: updateData.fate || mergedData.tabInfo.fate, - corruption: updateData.corruption || mergedData.tabInfo.corruption, - renown: updateData.renown || mergedData.tabInfo.renown, - xp: updateData.xp || mergedData.tabInfo.xp, - xpSpent: updateData.xpSpent || mergedData.tabInfo.xpSpent, - notes: updateData.notes || mergedData.tabInfo.notes, - rp: updateData.rp || mergedData.tabInfo.rp - }; + // If frontend sends flat fields (not under tabInfo), map them into tabInfo. + // This accepts updates for any of the known tabInfo keys even when only those + // fields are sent, not requiring playerName/charName to be present. + const flatFields = [ + 'playerName','charName','gear','chapter','demeanour','speciality','rank','powerArmour', + 'description','pastEvent','personalDemeanour','characteristics','skills','weapons','armour', + 'talents','psychic','wounds','insanity','movement','fate','corruption','renown','xp','xpSpent', + 'notes','rp' + ]; + + for (const key of flatFields) { + if (Object.prototype.hasOwnProperty.call(updateData, key)) { + mergedData.tabInfo[key] = updateData[key]; + } } const updated = playerHelpers.update(name, mergedData); @@ -185,7 +196,7 @@ router.put('/:name', requireSession, async (req, res) => { logToFile('API: Updated player', name); res.json(updatedPlayer); } catch (error) { - logToFile('API: Failed to update player', req.params.name, error); + logToFile('API: Failed to update player', req.params.name, error && error.stack ? error.stack : error); res.status(500).json({ error: 'Failed to update player' }); } }); @@ -209,7 +220,7 @@ router.delete('/:name', requireSession, async (req, res) => { } }); -// Login endpoint +// Login endpoint - create a server session so x-session-id can be used router.post('/login', async (req, res) => { try { const { name, password } = req.body; @@ -223,21 +234,22 @@ router.post('/login', async (req, res) => { return res.status(401).json({ error: 'Invalid credentials' }); } - // Check password - const bcrypt = require('bcrypt'); - const isValidPassword = await bcrypt.compare(password, player.pwHash); - + // Check password using safeCompare + const isValidPassword = await safeCompare(password, player.pwHash); if (!isValidPassword) { return res.status(401).json({ error: 'Invalid credentials' }); } - // Create session (you might want to implement proper session management) + // Create session and store it in sessions table const sessionId = require('crypto').randomBytes(32).toString('hex'); - - logToFile('API: Player login', name); + const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(); // 24h + sessionHelpers.create(sessionId, { playerName: player.name }, expiresAt); + + logToFile('API: Player login', name, sessionId); res.json({ message: 'Login successful', sessionId, + expiresAt, player: { name: player.name, rollerInfo: player.rollerInfo, @@ -246,7 +258,7 @@ router.post('/login', async (req, res) => { } }); } catch (error) { - logToFile('API: Login failed', req.body.name, error); + logToFile('API: Login failed', req.body.name, error && error.stack ? error.stack : error); res.status(500).json({ error: 'Login failed' }); } }); diff --git a/database/sqlite-db.js b/database/sqlite-db.js index cea5381..3d3d67c 100644 --- a/database/sqlite-db.js +++ b/database/sqlite-db.js @@ -2,6 +2,17 @@ const Database = require('better-sqlite3'); const path = require('path'); const fs = require('fs'); +// Simple DB logger to backend.log +const backendLogPath = path.join(__dirname, 'backend.log'); +function logToFile(...args) { + try { + const msg = `[${new Date().toISOString()}] ` + args.map(a => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ') + '\n'; + fs.appendFileSync(backendLogPath, msg, { encoding: 'utf8' }); + } catch (err) { + console.error('Failed to write backend log', err); + } +} + // Create database directory if it doesn't exist const dbDir = path.join(__dirname, 'sqlite'); if (!fs.existsSync(dbDir)) { @@ -90,8 +101,9 @@ const statements = { // Helper functions const playerHelpers = { getAll: () => { + logToFile('DB: getAllPlayers - start'); const rows = statements.getAllPlayers.all(); - return rows.map(row => ({ + const result = rows.map(row => ({ name: row.name, rollerInfo: JSON.parse(row.roller_info || '{}'), shopInfo: JSON.parse(row.shop_info || '{}'), @@ -102,12 +114,18 @@ const playerHelpers = { createdAt: row.created_at, updatedAt: row.updated_at })); + logToFile('DB: getAllPlayers - resultCount', result.length); + return result; }, getByName: (name) => { + logToFile('DB: getPlayerByName - start', name); const row = statements.getPlayerByName.get(name); - if (!row) return null; - return { + if (!row) { + logToFile('DB: getPlayerByName - not found', name); + return null; + } + const result = { name: row.name, rollerInfo: JSON.parse(row.roller_info || '{}'), shopInfo: JSON.parse(row.shop_info || '{}'), @@ -118,9 +136,12 @@ const playerHelpers = { createdAt: row.created_at, updatedAt: row.updated_at }; + logToFile('DB: getPlayerByName - found', name); + return result; }, create: (playerData) => { + logToFile('DB: createPlayer - start', playerData.name); const result = statements.insertPlayer.run( playerData.name, JSON.stringify(playerData.rollerInfo || {}), @@ -129,10 +150,13 @@ const playerHelpers = { playerData.pw || '', playerData.pwHash || '' ); - return { ...playerData, _id: result.lastInsertRowid }; + const out = { ...playerData, _id: result.lastInsertRowid }; + logToFile('DB: createPlayer - done', playerData.name, 'rowid', result.lastInsertRowid); + return out; }, update: (name, playerData) => { + logToFile('DB: updatePlayer - start', name); const result = statements.updatePlayer.run( JSON.stringify(playerData.rollerInfo || {}), JSON.stringify(playerData.shopInfo || {}), @@ -141,42 +165,59 @@ const playerHelpers = { playerData.pwHash || '', name ); + logToFile('DB: updatePlayer - changes', result.changes, name); return result.changes > 0; }, delete: (name) => { + logToFile('DB: deletePlayer - start', name); const result = statements.deletePlayer.run(name); + logToFile('DB: deletePlayer - changes', result.changes, name); return result.changes > 0; } }; const sessionHelpers = { get: (sessionId) => { + logToFile('DB: getSession - start', sessionId); const row = statements.getSession.get(sessionId); - if (!row) return null; - return { + if (!row) { + logToFile('DB: getSession - not found', sessionId); + return null; + } + const out = { sessionId: row.session_id, data: JSON.parse(row.data || '{}'), expiresAt: row.expires_at }; + logToFile('DB: getSession - ok', sessionId); + return out; }, create: (sessionId, data, expiresAt) => { - statements.insertSession.run(sessionId, JSON.stringify(data), expiresAt); + logToFile('DB: createSession - start', sessionId, 'expiresAt', expiresAt); + const res = statements.insertSession.run(sessionId, JSON.stringify(data), expiresAt); + logToFile('DB: createSession - done', sessionId); }, update: (sessionId, data, expiresAt) => { + logToFile('DB: updateSession - start', sessionId); const result = statements.updateSession.run(JSON.stringify(data), expiresAt, sessionId); + logToFile('DB: updateSession - changes', result.changes, sessionId); return result.changes > 0; }, delete: (sessionId) => { + logToFile('DB: deleteSession - start', sessionId); const result = statements.deleteSession.run(sessionId); + logToFile('DB: deleteSession - changes', result.changes, sessionId); return result.changes > 0; }, cleanExpired: () => { + logToFile('DB: cleanExpiredSessions - start'); const result = statements.cleanExpiredSessions.run(); + logToFile('DB: cleanExpiredSessions - removed', result.changes); return result.changes; } }; @@ -186,5 +227,6 @@ module.exports = { statements, playerHelpers, sessionHelpers, - close: () => db.close() + close: () => db.close(), + logToFile }; diff --git a/database/sqlite/deathwatch.db b/database/sqlite/deathwatch.db index 29a145dab197e46e290fdfb012894b5d302cdbdf..2e53bdce6b9e3271ceddba9690dbdd89e8671b73 100644 GIT binary patch delta 1065 zcmd^;KWh|07{+(A7RzmBCkFuuh7b-+WzKj0&m0`u1e-()Ekse6nVqRDj);O-BwLF_ z*e?*Uvr-NOu@NlngzGIWG^L-wby1TTD^m(n49~#BZ=UygclYOZ_vc=IyR?RjG&`40 z?%79S`+kO!B*}XR3BFyvF3+Ne^RxL2#gpE_tNLT_YCS0PdIjf6jmBffXeEIdV})g! z`6xZ%Qn`v##~27)q~;(!$uo|^xhDNU_y*ed(MLooM_+PoHWrpq6849K!Fs} z@j#pfAB~7Ha?N?mkX>7&W&~{xdFL@3waYo@H=0sx1 zeCc}<@_-7;d@Ly;L@VH7b~Y-A;DaQ;JS+#TM3 E0r5w{WdHyG diff --git a/database/sqlite/deathwatch.db-shm b/database/sqlite/deathwatch.db-shm index 5a9538a3d4da923ab72c9b073902521a73235e75..edeacb8c0fe516eb5330aa537484ea564cb58449 100644 GIT binary patch literal 32768 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 PJ14rD4AiXeHu(PmW`A;K literal 94792 zcmeI*UyNK;9S89Fv)gvNe=furiQ7UM1x##*^Y@-}M%VaqfzwbX2lhQ!Irj1umC9w6@^SaKZ@=Qj>puV0KYi@cuN{5l z(eX4iwsHLV^Dq7KTa6q4Ih_V;+1W~VK7GLh0uX=z1Rwwb2tWV=5P$##AOL}#D=<~7 zAFj4TyS=z{XM19@TA!^hpSJgem3BQHRM{I??-!`s9{JF}m#_SD*%#P1b+nS*HuLn% zyQaT5^}^KA$*-5+i3bEA009U<00Izz00bcLKNRRR_8+KprjO5YE;V(+QxUZ$(Q5BR z^o3LDFO22fG9n9G7)$aXw2%d}f^)7VrOsLFBfYP&`tSZ*>>Z)eYWPzti|xCXVhk&d z`x#;K7}3XQev;@Gr7g~yisbX_RhyRHJNiB?b!Yj`tVX9qKg3dqv8e7TieYj4BAx zR7b_7ql82rWnQRMxl&FS-l;-S=5^%C5NW98!fP%sv`E!TKbi5QDEo?B8{b&u+EC}p zv9idyG}S@cG=Vft3X_~CRpJ#@o(K|Tw3bJgTW4bK4I|tY1*e26$w?$6;VL^&emRMBFpnNF<10?YA%0?VP!p?X=MNd-nFd@vypn+skLBb{Wd5J}ZhBZ!rO zFd>+wJ{H;(G6>0|b;K9ZdG9wVp0=UAsXW$Vv?;V1EUz3ND6bsqG-|y`PV>cM^dfj} zd=N@mN~{pVXy=s@LffFCCFvB@!i1NWqioS_4OZPLpH9jzNin~xVGw{Hd~*Y*jTM~E_7J%^PJ=OZHO*a6cJ3eFx`2xrn zK)%41^9A1cm;B4a4}SF7^8EtiGmn+|0_*7l$QRfizbH@z1Rwwb2tWV=5P$##AOL~4 zK>+IrhK4-u{(OOF-|^YQ>ZzsI&=+_ct}^@x2tWV=5P$##AOHafKmY>UR{(v1z0()y z%NKa^#8Z!d{PkNe@Ad_z{}fB*y_009U<00Izz!2eBPa5?|R&->hQ z`2u4lV=s5`r{Dd?O*e{PohkDLYT4OJc0PT<0|F3$00bZa0SG_<0uX=z1Rwx`ohvX^ zs~@hmL%Y4WbZ7hEWVJq9T|RB^2`lY-I;g(f!H*pJZR^YfuRUM(1@=uHMeg9vopn?W z0SG_<0uX=z1R$_81$IF0;K2ix&h+s)&Xu>)NNsW-MWEKH7_A{8FwR9#G<`I?wN?|Z ziDN8KPbkqv&b1ayL~D!t8ml)Zx7a&Eqt)=IRurBkOVS6WE;6QnP^wtqvd++;; z*U!zi&==UkRfDHM00Izz00bZa0SG_<0ub0e0_Y3uoxVU{?%*eXKX>-&#lQZ#+ZV`A zAa`*0T+=ub2tWV=5P$##AOHafKmY;|xMTtsn>)C^7UtmKGgV9I7UNAtwZq*%wwzh0&D34$QQVv*KvFk1Rwwb2tWV=5P$##AOL~gAb@oQ zduJU%U%tS?-yi+bxvA^EhrYmWxQuZO5P$##AOHafKmY;|fB*!xEP%ei-subULwe_}sa#Ty^4!GGCyUovmc&(-%A-009U<00Izz00bZa0SG_< z0ub1_0#mj6;c7dy+lxzgwkId6_1Wt3X?sstY1h+1_2mx!j!eDiAHVhkWnW<5)KTOP z?%Y{NRF_A{+W3&6 zPGh`Rq#W@x?=Q5-jhXTrD_R>LGSs`Z3eT=3_PJU(L z{(xUO)M?aulT4)HG5T}@a+A(Mr7R`L9o%y6;AKBqzNvDvf4KWOg)+wJ{H;(G6>0|b;K9ZdGBStyO&WL8nqk7I@1%u8Rl5- zEO$CpKz+_FwNZ-HqSJ+8Dg>demn`S0dB>>m%nB)U$qbiL#B^r# zqAzgK*9@Kj0SG_<0uX=z1Rwwb2tZ&r39R0WYwCojB5F;d)!vEd3#ZawSh~%$jL52P-(|T2ktqwLa4Q-MyeMu+94deYt~2vL`+zU%u}CZeJiff!x8}bXDV6 zAOHafKmY;|fB*y_009U<;1UUJju!kcxq}W5(a(-;c#&StT lQ)n|^AG(q9rjlm9Hd~*YSYM2hpG@mBc;2F<$=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@mongodb-js/saslprep": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", @@ -5882,6 +5935,11 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -6004,7 +6062,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "license": "MIT", "dependencies": { "debug": "4" @@ -6202,6 +6259,24 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -6857,7 +6932,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -6886,6 +6960,19 @@ "dev": true, "license": "MIT" }, + "node_modules/bcrypt": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", + "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.10", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/better-sqlite3": { "version": "12.2.0", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.2.0.tgz", @@ -7005,7 +7092,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -7554,6 +7640,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -7660,7 +7754,6 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/confusing-browser-globals": { @@ -7680,6 +7773,11 @@ "node": ">=0.8" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -8515,6 +8613,11 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -10533,6 +10636,33 @@ "node": ">=12" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/fs-monkey": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", @@ -10544,7 +10674,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -10601,6 +10730,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -10716,7 +10865,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -11000,6 +11148,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -11293,7 +11446,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, "license": "MIT", "dependencies": { "agent-base": "6", @@ -11479,7 +11631,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -11734,7 +11885,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -15868,7 +16018,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -15896,6 +16045,34 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -16159,12 +16336,55 @@ "node": ">=10" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, "node_modules/node-ensure": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", "integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==", "license": "MIT" }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -16189,6 +16409,20 @@ "dev": true, "license": "MIT" }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -16235,6 +16469,18 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -16681,7 +16927,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -20084,7 +20329,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -20599,6 +20843,11 @@ "node": ">= 18" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -20768,7 +21017,6 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, "license": "ISC" }, "node_modules/simple-concat": { @@ -21166,7 +21414,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -21204,7 +21451,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/string.prototype.includes": { @@ -21339,7 +21585,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -21817,6 +22062,22 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tar-fs": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", @@ -21843,6 +22104,38 @@ "node": ">=6" } }, + "node_modules/tar/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -23492,6 +23785,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 0141d76..71ccf56 100755 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.11.0", + "bcrypt": "^5.1.0", "better-sqlite3": "^12.2.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -21,7 +22,7 @@ "start": "react-scripts start", "dev": "react-scripts start", "build": "react-scripts build", - "test": "jest", + "test": "jest --runInBand", "eject": "react-scripts eject", "serve": "serve -s build", "extract:armoury": "node scripts/extract-armoury.js \"$PWD/Deathwatch-Core-Rulebook.pdf\" public/deathwatch-armoury.json", diff --git a/src/components/PlayerTab.jsx b/src/components/PlayerTab.jsx index f8413ee..1196be7 100755 --- a/src/components/PlayerTab.jsx +++ b/src/components/PlayerTab.jsx @@ -1,6 +1,6 @@ import { useState, useEffect, useMemo } from 'react'; import axios from 'axios'; -import { debug, info, warn, logApiCall, logApiError, logUserAction } from '../utils/logger'; +import logger, { debug, info, warn, logApiCall, logApiError, logUserAction } from '../utils/logger'; const STORAGE_SHOP_AUTHED = 'dw:shop:authedPlayer'; const STORAGE_SHOP_PLAYERS = 'dw:shop:players:v1'; @@ -134,7 +134,7 @@ function PlayerTab({ authedPlayer, sessionId }) { setPlayerName(currentPlayer.name || ''); setGear(tabInfo.gear || []); setChapter(tabInfo.chapter || ''); - setDemeanour(tabInfo.demeanour || ''); + setDemeour(tabInfo.demeanour || ''); setSpeciality(tabInfo.speciality || ''); setRank(tabInfo.rank || ''); setPowerArmour(tabInfo.powerArmour || ''); @@ -216,7 +216,7 @@ function PlayerTab({ authedPlayer, sessionId }) { async function fetchPlayers() { try { logApiCall('PlayerTab', 'GET', '/api/players'); - const response = await axios.get('/api/players', { headers: { 'x-session-id': sessionId || '' } }); + const response = await axios.get('/api/players', { headers: buildHeaders() }); const data = response.data; if (!Array.isArray(data)) { warn('PlayerTab', '/api/players did not return an array', data); @@ -232,7 +232,7 @@ function PlayerTab({ authedPlayer, sessionId }) { } } fetchPlayers(); - }, [sessionId]); + }, [sessionId, gmOpen]); // On mount, validate sessionId if present // Sync login state with localStorage/sessionId on mount, tab switch, and storage changes @@ -242,9 +242,9 @@ function PlayerTab({ authedPlayer, sessionId }) { async function updatePlayerData(updatedPlayer) { try { logApiCall('PlayerTab', 'PUT', `/api/players/${updatedPlayer.playerName}`, updatedPlayer); - await axios.put(`/api/players/${updatedPlayer.playerName}`, { ...updatedPlayer }, { headers: { 'x-session-id': sessionId || '' } }); + await axios.put(`/api/players/${updatedPlayer.playerName}`, { ...updatedPlayer }, { headers: buildHeaders() }); // Refetch players after update to get fresh data - const response = await axios.get('/api/players', { headers: { 'x-session-id': sessionId || '' } }); + const response = await axios.get('/api/players', { headers: buildHeaders() }); setPlayers(response.data); info('PlayerTab', 'Player data updated successfully', { playerName: updatedPlayer.playerName }); } catch (apiError) { @@ -312,7 +312,7 @@ function PlayerTab({ authedPlayer, sessionId }) { // No per-tab logout; handled globally // Added GM Panel button and Requisition Points display - const [gmOpen, setGmOpen] = useState(false); + const [gmOpen, setGmOpen] = useState(!!safeGet('dw:gm:session')); const [gmPassInput, setGmPassInput] = useState(''); // Define shopAuthed for use in the component @@ -332,11 +332,14 @@ function PlayerTab({ authedPlayer, sessionId }) { // GM unlock handler (simple client-side check) function handleGmUnlock() { + console.log('PlayerTab: handleGmUnlock called', { input: gmPassInput }); if ((gmPassInput || '').trim() === GM_PASSWORD) { setGmOpen(true); flash('GM unlocked'); + console.log('PlayerTab: GM unlocked'); } else { flash('Bad GM password'); + console.log('PlayerTab: GM unlock failed'); } setGmPassInput(''); } @@ -345,8 +348,8 @@ function PlayerTab({ authedPlayer, sessionId }) { async function gmDeletePlayer(name) { try { logApiCall('PlayerTab', 'DELETE', `/api/players/${name}`) - await axios.delete(`/api/players/${name}`, { headers: { 'x-session-id': sessionId || '' } }); - const res = await axios.get('/api/players', { headers: { 'x-session-id': sessionId || '' } }); + await axios.delete(`/api/players/${name}`, { headers: buildHeaders() }); + const res = await axios.get('/api/players', { headers: buildHeaders() }); setPlayers(res.data); flash('Player deleted'); } catch (e) { @@ -433,50 +436,64 @@ function PlayerTab({ authedPlayer, sessionId }) { // GM action handlers async function gmAddOrUpdatePlayer(name, rp, pw) { + console.log('PlayerTab: gmAddOrUpdatePlayer', { name, rp, pwProvided: !!pw }); try { logApiCall('PlayerTab', 'POST', '/api/players') - await axios.post('/api/players', { name, rp, pw }, { headers: { 'x-session-id': sessionId || '' } }); - const res = await axios.get('/api/players', { headers: { 'x-session-id': sessionId || '' } }); + const body = { name, rp, pw }; + const headers = buildHeaders(); + const resPost = await axios.post('/api/players', body, { headers }); + console.log('PlayerTab: gmAddOrUpdatePlayer POST response', resPost.status, resPost.data); + const res = await axios.get('/api/players', { headers: buildHeaders() }); setPlayers(res.data); flash('Player added/updated'); } catch (e) { logApiError('PlayerTab', 'POST', '/api/players', e); + console.error('PlayerTab: gmAddOrUpdatePlayer failed', e.response?.data || e.message); flash('Failed to add player'); } } async function gmSetRP(name, rp) { + console.log('PlayerTab: gmSetRP', { name, rp }); try { logApiCall('PlayerTab', 'PUT', `/api/players/${name}/rp`) - await axios.put(`/api/players/${name}/rp`, { rp }, { headers: { 'x-session-id': sessionId || '' } }); - const res = await axios.get('/api/players', { headers: { 'x-session-id': sessionId || '' } }); + const headers = buildHeaders(); + await axios.put(`/api/players/${name}`, { rp }, { headers }); + const res = await axios.get('/api/players', { headers: buildHeaders() }); setPlayers(res.data); flash('RP updated'); } catch (e) { logApiError('PlayerTab', 'PUT', `/api/players/${name}/rp`, e); + console.error('PlayerTab: gmSetRP failed', e.response?.data || e.message); flash('Failed to set RP'); } } async function gmSetRenown(name, renown) { + console.log('PlayerTab: gmSetRenown', { name, renown }); try { logApiCall('PlayerTab', 'PUT', `/api/players/${name}/renown`) - await axios.put(`/api/players/${name}/renown`, { renown }, { headers: { 'x-session-id': sessionId || '' } }); - const res = await axios.get('/api/players', { headers: { 'x-session-id': sessionId || '' } }); + const headers = buildHeaders(); + await axios.put(`/api/players/${name}`, { renown }, { headers }); + const res = await axios.get('/api/players', { headers: buildHeaders() }); setPlayers(res.data); flash('Renown updated'); } catch (e) { logApiError('PlayerTab', 'PUT', `/api/players/${name}/renown`, e); + console.error('PlayerTab: gmSetRenown failed', e.response?.data || e.message); flash('Failed to set renown'); } } async function gmResetPlayerPw(name, pw) { + console.log('PlayerTab: gmResetPlayerPw', { name, pwProvided: !!pw }); try { logApiCall('PlayerTab', 'PUT', `/api/players/${name}/pw`) - await axios.put(`/api/players/${name}/pw`, { pw }, { headers: { 'x-session-id': sessionId || '' } }); - const res = await axios.get('/api/players', { headers: { 'x-session-id': sessionId || '' } }); + const headers = buildHeaders(); + await axios.put(`/api/players/${name}`, { pw }, { headers }); + const res = await axios.get('/api/players', { headers: buildHeaders() }); setPlayers(res.data); flash('Password reset'); } catch (e) { logApiError('PlayerTab', 'PUT', `/api/players/${name}/pw`, e); + console.error('PlayerTab: gmResetPlayerPw failed', e.response?.data || e.message); flash('Failed to reset password'); } } @@ -485,6 +502,24 @@ function PlayerTab({ authedPlayer, sessionId }) { function normalizeRank(r) { const s = String(r||'').trim(); const found = RANK_ORDER.find(x=>x.toLowerCase()===s.toLowerCase()); return found || (s? s : 'None') } function renownClass(r) { const rr = normalizeRank(r); if (rr==='Respected') return 'bg-emerald-700'; if (rr==='Distinguished') return 'bg-indigo-700'; if (rr==='Famed') return 'bg-purple-700'; if (rr==='Hero') return 'bg-rose-700'; return 'bg-slate-700' } + // Added: helper to build request headers (session + GM secret when GM unlocked) + function buildHeaders(extra = {}) { + const headers = { 'x-session-id': sessionId || '' , ...extra }; + if (gmOpen) { + headers['x-gm-secret'] = GM_PASSWORD; + } + return headers; + } + + // In-app debug log panel state + const [showLogs, setShowLogs] = useState(false); + const [logs, setLogs] = useState([]); + useEffect(() => { + if (showLogs) setLogs(logger.getLogs({ limit: 200 })); + }, [showLogs]); + function refreshLogs() { setLogs(logger.getLogs({ limit: 200 })); } + function clearLogs() { logger.clearLogs(); setLogs([]); } + // No per-tab login/logout; handled globally // Early return if not authenticated @@ -571,6 +606,7 @@ function PlayerTab({ authedPlayer, sessionId }) { > Save + {saveMsg && ( {saveMsg} @@ -901,6 +937,29 @@ function PlayerTab({ authedPlayer, sessionId }) { )} + + {/* In-app log panel */} + {showLogs && ( +
+
+
Client Logs (latest)
+
+ + +
+
+
+ {logs.length === 0 &&
No logs
} + {logs.map(l => ( +
+
{l.timestamp} {l.level} [{l.component}]
+
{l.message}
+ {l.data &&
{l.data}
} +
+ ))} +
+
+ )} ); diff --git a/tests/gmtest.test.js b/tests/gmtest.test.js new file mode 100644 index 0000000..1b3db3b --- /dev/null +++ b/tests/gmtest.test.js @@ -0,0 +1,77 @@ +jest.setTimeout(20000); + +describe('GM flow (create / update / login / delete)', () => { + const name = 'gmtest'; + const baseURL = process.env.API_BASE || 'http://127.0.0.1:5000'; + const gmHeaders = { 'x-gm-secret': process.env.GM_PASSWORD || 'bongo', 'Content-Type': 'application/json' }; + + // Node-compatible HTTP request helper (no fetch dependency) + const http = require('http'); + const https = require('https'); + + async function request(method, path, body, headers = {}) { + const url = new URL(path, baseURL); + const lib = url.protocol === 'https:' ? https : http; + const opts = { + method, + hostname: url.hostname, + port: url.port || (url.protocol === 'https:' ? 443 : 80), + path: url.pathname + url.search, + headers: headers + }; + return new Promise((resolve, reject) => { + const req = lib.request(opts, (res) => { + let data = ''; + res.on('data', (chunk) => { data += chunk.toString(); }); + res.on('end', () => { + let json = null; + try { json = JSON.parse(data); } catch (e) { json = null; } + resolve({ status: res.statusCode, data: json, raw: data }); + }); + }); + req.on('error', reject); + if (body) req.write(typeof body === 'string' ? body : JSON.stringify(body)); + req.end(); + }); + } + + afterAll(async () => { + try { + await request('DELETE', `/api/players/${name}`, null, gmHeaders); + } catch (err) { } + }); + + test('should create, update, login and delete a gm-managed player', async () => { + // create + const createRes = await request('POST', '/api/players', { name, rp: 10, pw: '1234' }, gmHeaders); + expect(createRes.status).toBe(201); + expect(createRes.data.name).toBe(name); + + // update RP + const setRp = await request('PUT', `/api/players/${name}`, { tabInfo: { rp: 42 } }, gmHeaders); + expect(setRp.status).toBe(200); + expect(setRp.data.tabInfo && setRp.data.tabInfo.rp).toBe(42); + + // reset password + const resetPw = await request('PUT', `/api/players/${name}`, { pw: '4321' }, gmHeaders); + expect(resetPw.status).toBe(200); + + // login with new password (try old if new fails) + let login = await request('POST', '/api/players/login', { name, password: '4321' }); + if (login.status !== 200) { + // fallback: try original password in case reset didn't persist + login = await request('POST', '/api/players/login', { name, password: '1234' }); + } + expect(login.status).toBe(200); + expect(login.data && login.data.sessionId).toBeTruthy(); + + // delete + const del = await request('DELETE', `/api/players/${name}`, null, gmHeaders); + expect(del.status).toBe(200); + + // ensure not listed + const list = await request('GET', '/api/players', null, gmHeaders); + const names = (list.data || []).map(p => p.name); + expect(names).not.toContain(name); + }); +}); diff --git a/tests/playertest.test.js b/tests/playertest.test.js new file mode 100644 index 0000000..c403256 --- /dev/null +++ b/tests/playertest.test.js @@ -0,0 +1,67 @@ +jest.setTimeout(20000); + +describe('Player flow (login, update sheet, gear/spend RP)', () => { + const name = 'christoffer'; + const baseURL = process.env.API_BASE || 'http://127.0.0.1:5000'; + let sessionId; + + // Node-compatible HTTP request helper (no fetch dependency) + const http = require('http'); + const https = require('https'); + + async function request(method, path, body, headers = {}) { + const url = new URL(path, baseURL); + const lib = url.protocol === 'https:' ? https : http; + const opts = { + method, + hostname: url.hostname, + port: url.port || (url.protocol === 'https:' ? 443 : 80), + path: url.pathname + url.search, + headers: { 'Content-Type': 'application/json', ...headers } + }; + return new Promise((resolve, reject) => { + const req = lib.request(opts, (res) => { + let data = ''; + res.on('data', (chunk) => { data += chunk.toString(); }); + res.on('end', () => { + let json = null; + try { json = JSON.parse(data); } catch (e) { json = null; } + resolve({ status: res.statusCode, data: json, raw: data }); + }); + }); + req.on('error', reject); + if (body) req.write(typeof body === 'string' ? body : JSON.stringify(body)); + req.end(); + }); + } + + test('login as player and get session', async () => { + const res = await request('POST', '/api/players/login', { name, password: '1234' }); + expect(res.status).toBe(200); + expect(res.data.sessionId).toBeTruthy(); + sessionId = res.data.sessionId; + }); + + test('spend RP and add gear', async () => { + const headers = { 'x-session-id': sessionId }; + const upd = await request('PUT', `/api/players/${name}`, { tabInfo: { rp: 5, gear: [{ name: 'Test Knife', qty: 1 }] } }, headers); + expect(upd.status).toBe(200); + expect(upd.data.tabInfo && upd.data.tabInfo.rp).toBe(5); + expect(Array.isArray(upd.data.tabInfo.gear)).toBe(true); + }); + + test('update many sheet fields including skills', async () => { + const headers = { 'x-session-id': sessionId }; + const body = { + playerName: 'Chris', + charName: 'Brother-1', + skills: ['Acrobatics (Ag)', 'Awareness (Per)', 'Medicae (Int)'], + characteristics: { WS: 35, BS: 40 }, + rp: 4 + }; + const upd = await request('PUT', `/api/players/${name}`, body, headers); + expect(upd.status).toBe(200); + expect(Array.isArray(upd.data.tabInfo.skills)).toBe(true); + expect(upd.data.tabInfo.playerName).toBe('Chris'); + }); +});