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 29a145d..2e53bdc 100644 Binary files a/database/sqlite/deathwatch.db and b/database/sqlite/deathwatch.db differ diff --git a/database/sqlite/deathwatch.db-shm b/database/sqlite/deathwatch.db-shm index 5a9538a..edeacb8 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 67d623d..4ee0cf9 100644 Binary files a/database/sqlite/deathwatch.db-wal and b/database/sqlite/deathwatch.db-wal differ diff --git a/jest.config.js b/jest.config.js index ce90c0c..51011af 100755 --- a/jest.config.js +++ b/jest.config.js @@ -11,4 +11,10 @@ module.exports = { configFile: './babel.config.js', }, }, + // Restrict Jest to only run tests under the top-level tests/ folder to avoid + // parsing ESM/React source files in src/ during CI runs of backend tests. + testMatch: ['**/tests/**/*.test.js'], + testEnvironmentOptions: { + node: true + } }; diff --git a/package-lock.json b/package-lock.json index 9b11aa2..6e87bc1 100755 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,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", @@ -4087,6 +4088,58 @@ "dev": true, "license": "MIT" }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=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'); + }); +});