From b6320ceff6a9b764f73829cd7692f7de09a76cef Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 16 Aug 2025 13:01:30 +0200 Subject: [PATCH] Remove MongoDB; add PM2 scripts, CI, tests; fix PlayerTab hooks; add pm2 smoke-test --- .github/workflows/ci.yml | 23 ++ .gitignore | 1 - README.md | 1 - data/WiredTiger | 2 - data/WiredTiger.lock | 1 - data/WiredTiger.turtle | 6 - data/WiredTiger.wt | Bin 49152 -> 0 bytes data/WiredTigerHS.wt | Bin 4096 -> 0 bytes data/_mdb_catalog.wt | Bin 20480 -> 0 bytes data/collection-0--8172258859796687419.wt | Bin 20480 -> 0 bytes data/collection-2--8172258859796687419.wt | Bin 36864 -> 0 bytes data/collection-4--8172258859796687419.wt | Bin 4096 -> 0 bytes .../metrics.2025-08-14T21-20-42Z-00000 | Bin 40266 -> 0 bytes .../metrics.2025-08-14T21-29-23Z-00000 | Bin 40179 -> 0 bytes data/index-1--8172258859796687419.wt | Bin 20480 -> 0 bytes data/index-3--8172258859796687419.wt | Bin 36864 -> 0 bytes data/index-5--8172258859796687419.wt | Bin 4096 -> 0 bytes data/index-6--8172258859796687419.wt | Bin 4096 -> 0 bytes data/mongod.lock | 0 data/sizeStorer.wt | Bin 36864 -> 0 bytes data/storage.bson | Bin 114 -> 0 bytes database/backend.log | 305 ++++++++++++++++++ database/fix_player_passwords.js | 31 -- database/gmSessionModel.js | 19 -- database/migrate-to-sqlite.js | 166 ---------- database/mongo-backup.json | 45 --- database/playerModel.js | 14 - database/scripts/pm2_smoke_test.sh | 28 ++ database/server-mongo-backup.js | 75 ----- database/sqlite/deathwatch.db-shm | Bin 32768 -> 32768 bytes database/sqlite/deathwatch.db-wal | Bin 2826352 -> 3427872 bytes package-lock.json | 182 +---------- package.json | 13 +- scripts/dbinstall.js | 40 --- scripts/install-mongodb.js | 26 -- src/components/PlayerTab.jsx | 12 +- tests/rules.test.js | 43 +++ tests/shop.test.js | 42 +++ 38 files changed, 453 insertions(+), 622 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100755 data/WiredTiger delete mode 100755 data/WiredTiger.lock delete mode 100755 data/WiredTiger.turtle delete mode 100755 data/WiredTiger.wt delete mode 100755 data/WiredTigerHS.wt delete mode 100755 data/_mdb_catalog.wt delete mode 100755 data/collection-0--8172258859796687419.wt delete mode 100755 data/collection-2--8172258859796687419.wt delete mode 100755 data/collection-4--8172258859796687419.wt delete mode 100755 data/diagnostic.data/metrics.2025-08-14T21-20-42Z-00000 delete mode 100755 data/diagnostic.data/metrics.2025-08-14T21-29-23Z-00000 delete mode 100755 data/index-1--8172258859796687419.wt delete mode 100755 data/index-3--8172258859796687419.wt delete mode 100755 data/index-5--8172258859796687419.wt delete mode 100755 data/index-6--8172258859796687419.wt delete mode 100755 data/mongod.lock delete mode 100755 data/sizeStorer.wt delete mode 100755 data/storage.bson delete mode 100644 database/fix_player_passwords.js delete mode 100755 database/gmSessionModel.js delete mode 100644 database/migrate-to-sqlite.js delete mode 100644 database/mongo-backup.json delete mode 100755 database/playerModel.js create mode 100644 database/scripts/pm2_smoke_test.sh delete mode 100755 database/server-mongo-backup.js delete mode 100755 scripts/dbinstall.js delete mode 100755 scripts/install-mongodb.js create mode 100644 tests/rules.test.js create mode 100644 tests/shop.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f581ece --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + - name: Install dependencies + run: npm ci + - name: Run tests + run: npm test -- --runInBand + - name: Build production bundle + run: npm run build diff --git a/.gitignore b/.gitignore index 3b1aed1..c79767b 100755 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -mongodb.log Deathwatch-Core-Rulebook.pdf data/journal/* diff --git a/README.md b/README.md index b0ff56a..6a1d468 100755 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ Deathwatch Roller is a React-based application designed to assist players and Ga - Player management - Requisition shop for items - Session tracking -- Integration with MongoDB for data persistence ## Setup Instructions 1. Clone the repository: diff --git a/data/WiredTiger b/data/WiredTiger deleted file mode 100755 index 94fbc2b..0000000 --- a/data/WiredTiger +++ /dev/null @@ -1,2 +0,0 @@ -WiredTiger -WiredTiger 10.0.2: (December 21, 2021) diff --git a/data/WiredTiger.lock b/data/WiredTiger.lock deleted file mode 100755 index 3d84206..0000000 --- a/data/WiredTiger.lock +++ /dev/null @@ -1 +0,0 @@ -WiredTiger lock file diff --git a/data/WiredTiger.turtle b/data/WiredTiger.turtle deleted file mode 100755 index 2ad5f58..0000000 --- a/data/WiredTiger.turtle +++ /dev/null @@ -1,6 +0,0 @@ -WiredTiger version string -WiredTiger 10.0.2: (December 21, 2021) -WiredTiger version -major=10,minor=0,patch=2 -file:WiredTiger.wt -access_pattern_hint=none,allocation_size=4KB,app_metadata=,assert=(commit_timestamp=none,durable_timestamp=none,read_timestamp=none,write_timestamp=off),block_allocation=best,block_compressor=,cache_resident=false,checksum=on,collator=,columns=,dictionary=0,encryption=(keyid=,name=),format=btree,huffman_key=,huffman_value=,id=0,ignore_in_memory_cache_size=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=4KB,key_format=S,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=32KB,leaf_value_max=0,log=(enabled=true),memory_page_image_max=0,memory_page_max=5MB,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=false,prefix_compression_min=4,readonly=false,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,tiered_object=false,tiered_storage=(auth_token=,bucket=,bucket_prefix=,cache_directory=,local_retention=300,name=,object_target_size=0),value_format=S,verbose=[],version=(major=1,minor=1),write_timestamp_usage=none,checkpoint=(WiredTigerCheckpoint.11=(addr="018581e4dc06b8b98681e4928c41608781e41f76d752808080e29fc0e22fc0",order=11,time=1755206972,size=24576,newest_start_durable_ts=0,oldest_start_ts=0,newest_txn=4,newest_stop_durable_ts=0,newest_stop_ts=-1,newest_stop_txn=-11,prepare=0,write_gen=29,run_write_gen=25)),checkpoint_backup_info=,checkpoint_lsn=(4,4864) diff --git a/data/WiredTiger.wt b/data/WiredTiger.wt deleted file mode 100755 index 776e131a951d159df3e91d133fbf3e00faebba88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49152 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 x5JO9)bC+H5)`d4A1>o{~zxd(7<;Qs=0Hx&Q? diff --git a/data/WiredTigerHS.wt b/data/WiredTigerHS.wt deleted file mode 100755 index 3f019cba9b5ed0b1a0275084adb91169387c9683..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeIuF%bYD32 diff --git a/data/_mdb_catalog.wt b/data/_mdb_catalog.wt deleted file mode 100755 index 3776352fb895ea715d518049ecef92afeecd4117..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 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 diff --git a/data/collection-2--8172258859796687419.wt b/data/collection-2--8172258859796687419.wt deleted file mode 100755 index 69ec4bcf3efdf5a28a238c1c81f5354f437c1755..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36864 zcmeI5ZEPGz8OL|eHaXK6IjsXuxeLB?+)|=tZg=i( z<9%aa{1O$(u1b-Lpb-*M2@;>GDk@(p#7k9yGpi&`c)kYdVl9X_cXMc*v^`_ zRb+(CR+|Br4;ox5!%6Aml6X8Ljgn?NR8yPREzB%Hs~w(iyQqPg0wfYz?G*BxOd4K~ zHEk1*Ap@_C73@{Zu#x7Ct$3=f;V}(EtySc!O~JJd1G{4y@)4YLy8|W#l@53bP1ge5 z&~&SAGe0;0jjv$W(`}3S1e9e*#nCL2Xz2dwICmIwrED#e8&9E3N>bHaZL*d@shq0S zvZ^N5C+pdagvBgE6HKRRYk`41ri*lxK!yQUgCR^eBz@HkGnm9#w1Pa<)g8ZRHHgKe zYtue=)&pSc-W(a)ZIGp5$HmY9)|k$ClzG=h>&ze&_KM+Yy(xGNDJe=3MDXaa9c+1? z!8`{(Ougz zvE?(bgUty|_k3Uwjvm~{@&ic}Gf_Tr)g}YLnYCP2$wH>8G7==mY{wrnZL48xHQ%-k zwTX0VY$ZKbgF{1Ak8|+E4V{>t9_DhRW$^6j4GT^-9pqw2S~k>Pw!r@c2fWloP=86cR*<1!cZTQ;0$%_3M*Iz zml9TW&2P#nDc2fL;9*T54rd7dx`Q=bC!-378Wz<9OZCCgqs+E%2^v-nWc4igOp8mWd-fd@fudc>55My;}rH}p5t9?xOQCQ2J<9awndyJG|F~$ zxWdIJV2k*XVBu9k^U3;6z#B#b`^q(!hB)8$!mAp<4Ax+(9S58mu9qHz0cs^bQ=BVR zmBmU?f#bqlsWLZR;Y9A#^g>aoRP)ujX|7Z#PA#6`l!wZviY28|UR;OFD}G2=F2BYOO;s|HYrg``IE)g>77+00%01uou4~VDi)Od^mMUOfj5?#?t9GrutJjIa)cZctxb8hRj!?dg0``C&V7zUF_F^3?DohCN1i;NlDtzyq-5>ohvv8uaR ztLhExLM8g31SkPYfD)htD1nU`xz_%sE#tU2bJ8=F_2 zdH20%Z$fvp2}*zxpaduZN`Mle1SkPYfD)htC;>`<68IkyAir`Yzqf~b{_y_)^qVt> z$^HN3zudWTY3r@6jSWc92PHrWPy&-?0HRh+Cu&d&ToPisH=Bgn?#)|ub$@3?hzM^P)*hBzgVtkv zTH&s?D|;4|j^E;aPFo3ryLz zcomcjL^A-nwh@??C-WMFi^D7+cU>0w8_!C_O0M}X#(XoV*G*(8FmdvB0s_q=xzI1kY@h9T<~G86!1DiHMvy>I71yVuL|L2E!13o^XG?b;4V zIl}5>hRLb%Sicq~8px4TB5z<+-`Q7~Zub)=w;M`J&{CKc8WlF4&76chyCDxnk;Ym$ zieWxBFg#Z`TSF0dk^*>l6*$=4Q{aGdVN&FM9Ydt7Vut~^ag%Xg9E2+m zp}=n{zP*gWy=sBFjQ!S8iAD+j6}m6j8OG*=n}p&4@Mj;qnHZH8#c0LzQCCVFmL2+t zILt@nBV4>^w%%&57kOxD zI~sU;dqs_=z%Wg7lmBgGxQmOGwwx*@BIkEkt)>V=GKgQdxhRImS zHA8Mmn#MDHXihFQDe(}Q4(nYBMN7)|wZwyofpGkGiBEFky8fEgP5^yp_j%hbLIIp6 zq*~{NRmsUhD#gQ$5M>E86yX8Qhn(hNDJkuAYIykj?B?c`7b`sX*yfePGr#=wM)

P00E-tCyypb@H*jmpey?Q{}Vy>S#tk>BmMsY zqTqwa#bfVe6Ld2Gx*5PV+Xn2v89?#K(2lJD@&mKi*$H6q)vM*3y%PZY;MMQlnmYk> z_g^jF?0o=w&d*#_$Ws|+3CV$Wy{KEYl$lP}c^1IJ4{l#Y-B8tDE7N5PlaIF-- ztNye5S+<^p!FNA2_-ppipp?`Ey{DPmBd0&|^@Bt*-(`2-(RGm#jvr?FW6gZeu`gd_ z50WoB^1{7sX?)&epX|g|ezrfB#Sg9zv{do=ze`6ud-{{0g5UhjRsFxGg74BPYiGYps@yf%7s=TVzj^UP2-)#d&IiiafB)moqV>{i zFMLQ5`^$g6w=H33zm(~EY~z)YAGLPW`P5|c*xBUW=aYSt*|a#3{?7U2!Q7=spSy5A z**ke$N{_NjOa~ORPxWrV&SBx0m;yPd@N`GP!!L zV=&ptbl{hAd-k${3jd*hGtb8#2BMDy^qCT%1SkPYfD)htZgB!f;_>a({}=w6fWHtB z>;E6$yz*>zapo2`g$|DrpaduZN`Mle1SkPYfD)htC;>`<5}*Y3AwYJBC!5Q+;{V_I ze157G{|~|cbOV5W_|alYfD)htC;>`<5}*Vq0ZM=ppaduZO5meFVDJ6^x0zr3uyy}` zY3uSmTN|4fF56p?DK9VGSZ+=?0oZ2~04nIGg8u(!LH~~JFaAwAf0~Hz$xSy* Z_$Yi*p<|>3C;>`<5}*Vqfq#|2KLE4{0=EDF diff --git a/data/collection-4--8172258859796687419.wt b/data/collection-4--8172258859796687419.wt deleted file mode 100755 index 3f019cba9b5ed0b1a0275084adb91169387c9683..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeIuF%bYD32 diff --git a/data/diagnostic.data/metrics.2025-08-14T21-20-42Z-00000 b/data/diagnostic.data/metrics.2025-08-14T21-20-42Z-00000 deleted file mode 100755 index ac441aee7fa27d6a82b9eff02955efb78fb3521c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40266 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 diff --git a/data/diagnostic.data/metrics.2025-08-14T21-29-23Z-00000 b/data/diagnostic.data/metrics.2025-08-14T21-29-23Z-00000 deleted file mode 100755 index a1f00667f69ab7a3141c72c2f9cee099cb67c31b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40179 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 PKmY;|fB*#kQ-Qw#6Z`St diff --git a/data/index-5--8172258859796687419.wt b/data/index-5--8172258859796687419.wt deleted file mode 100755 index 3f019cba9b5ed0b1a0275084adb91169387c9683..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeIuF%bYD32 diff --git a/data/index-6--8172258859796687419.wt b/data/index-6--8172258859796687419.wt deleted file mode 100755 index 3f019cba9b5ed0b1a0275084adb91169387c9683..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeIuF%bYD32 diff --git a/data/mongod.lock b/data/mongod.lock deleted file mode 100755 index e69de29..0000000 diff --git a/data/sizeStorer.wt b/data/sizeStorer.wt deleted file mode 100755 index 69c4ad047814d2ccde996579a805323d365644cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36864 zcmeI4&x;&I6vt=Pm_#GSK!Wii#DhDeHg@;?R_d@D2uR39B|<`IYPxG?JKMkLu1T_< zW+dvtzrY2-_z&cwcoODd@VFjBKmzL^c!?Li`>KD;?9Q4fdePZ$553*>s_Ip}PgONr zO~1E#f2p!msZ{PCdF>mz-~a&-009sH0T2KI5C8!X009sH0T5VV0#6mTy+|(uo~8T4 z&(LduQYo$=00JNY0w4eaAOHd&00JNY0wA!+1j@a8os87)0_Yt;w*No;<)a6B4}Pf9 zu6!~aP2|zDcNduhYy$xh009sH0T2KI5C8!X009sHfrCK6A|s!pllJ`C{{Qli7f$N^ z|IY8P47bM*$HO5daDV^^fB*=900@8p2!H?xfB*=900{iA37n(e>+k=|{r?ZY{G68i z|KaHN&Ku+0yzfrQ&`CC9{VhLV-jJJ~%ULJ_$*ZjEN6v;FNF}r*fL#=nF7F9Hm2=tr zs&E{crgkipl1XTc1qp^ynbHoLdG!r2(awiouIJ#s${i*z~&coZ`2Dj`&+KJo`am~!TNnd^i|Ho3tb zkG~fsfl$1w5-HjGpw|mTXwwMsY2t?P2a+=yGK+crFiIqKh_D7xvPs?@+L{b_ZV8z{ z<05>U5CwK1Zc*z~8R`8qkxB+34V~n4BWI;!(U)0qJ(Rj?(bqwi=!=+}2J@xp?QSbe z&)Uh7Q$;n4G-aa9sG=|Aox+!8s7K$;x=>|>by-C(DEndTk!#x;m)2O678G1BQKfxT zVNQTXzvtbu%Xy%eL5^#Gh8=hzuQNUGqR`(g%B8VSQ`VI-mc*X;JL{hB65Bl)u}tPN zVn^|g!IUQx>Dp2EIyux7DhpXk(}oP>%VMC`Z53@unp@q0vmt3N=2tuKWjS}rPSQf7 z&zW9wK22stQ&}5TGmLy)v%D!=iKH)8K0%Bs%dt<#@rF#gQ7ZY>Ym9Ew?CB7Q>oku| zMn>vumCda-dywkx>NS6D{BX8d`11SIFZVsKFO&1r<#f&@!(2DvXAHAVKUsH$lx@># zwJn`88}(+_HJfc?_H%lUq0cjn9F?T*i+)sav(;!=Mzh|ivuwV%&5p%Fd6Sm8P0x{p zhNpalq%<&*@6K|RmK0^xt#Fng`tja+Q;n2C)mF^1URG3BUNKEZBNB^5lA;_>pGK|5 zXdT(Jv{|iwwlo?|9y}U-|KVtI^1`>LhS{+_-Yy=@`QsUXK>!3m00ck)1V8`;K;Vf4 zUM_Oy{r|U|D|ht%f06wE%UB1qm;%k0o(Kn-AOHd&00JNY0w4eaAOHd&00JPeAOubq zcFg<#Ut-7Z>HUA^|9^WlxpU?Gl?5>wW`F<)fB*=900@8p2!H?xfB*=9z=0)DBg>zs zv;6-5x7SzK%J2W_<9`GIIIuGVhJyeIfB*=900@8p2!H?xfB*s{}*4Mo|OCl z?eWg5LPrn)3U!eAGQ*)C4*fGv{{!aGzbp2|zjWuXY3Ke{O*WlQ zr*Q3;mbEyJ{kup#F-pj?$*h{yX;!a|fBMT<3D&My2@MU { console.error(e); process.exit(1); }); diff --git a/database/gmSessionModel.js b/database/gmSessionModel.js deleted file mode 100755 index 38fe686..0000000 --- a/database/gmSessionModel.js +++ /dev/null @@ -1,19 +0,0 @@ -const mongoose = require('mongoose'); - -const gmSessionSchema = new mongoose.Schema({ - sessionId: { - type: String, - required: true, - unique: true, - }, - isActive: { - type: Boolean, - default: true, - }, - createdAt: { - type: Date, - default: Date.now, - }, -}); - -module.exports = mongoose.model('GMSession', gmSessionSchema); diff --git a/database/migrate-to-sqlite.js b/database/migrate-to-sqlite.js deleted file mode 100644 index 273a01d..0000000 --- a/database/migrate-to-sqlite.js +++ /dev/null @@ -1,166 +0,0 @@ -const mongoose = require('mongoose'); -const Player = require('./playerModel'); -const { playerHelpers, sessionHelpers } = require('./sqlite-db'); - -require('dotenv').config(); - -const MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost:27017/deathwatch'; - -async function migratePlayers() { - try { - // Connect to MongoDB - console.log('Connecting to MongoDB...'); - await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true }); - console.log('Connected to MongoDB'); - - // Fetch all players from MongoDB - console.log('Fetching players from MongoDB...'); - const mongoPlayers = await Player.find(); - console.log(`Found ${mongoPlayers.length} players in MongoDB`); - - // Migrate each player to SQLite - let migratedCount = 0; - let skippedCount = 0; - - for (const mongoPlayer of mongoPlayers) { - try { - // Check if player already exists in SQLite - const existingPlayer = playerHelpers.getByName(mongoPlayer.name); - - if (existingPlayer) { - console.log(`Player ${mongoPlayer.name} already exists in SQLite, updating...`); - const updated = playerHelpers.update(mongoPlayer.name, { - rollerInfo: mongoPlayer.rollerInfo || {}, - shopInfo: mongoPlayer.shopInfo || {}, - tabInfo: mongoPlayer.tabInfo || {}, - pw: mongoPlayer.pw || '', - pwHash: mongoPlayer.pwHash || '' - }); - - if (updated) { - console.log(`✓ Updated player: ${mongoPlayer.name}`); - migratedCount++; - } else { - console.log(`⚠ Failed to update player: ${mongoPlayer.name}`); - skippedCount++; - } - } else { - // Create new player in SQLite - const newPlayer = playerHelpers.create({ - name: mongoPlayer.name, - rollerInfo: mongoPlayer.rollerInfo || {}, - shopInfo: mongoPlayer.shopInfo || {}, - tabInfo: mongoPlayer.tabInfo || {}, - pw: mongoPlayer.pw || '', - pwHash: mongoPlayer.pwHash || '' - }); - - console.log(`✓ Migrated player: ${mongoPlayer.name}`); - migratedCount++; - } - - // Log the data being migrated - console.log(` - Roller info: ${Object.keys(mongoPlayer.rollerInfo || {}).length} keys`); - console.log(` - Shop info: ${Object.keys(mongoPlayer.shopInfo || {}).length} keys`); - console.log(` - Tab info: ${Object.keys(mongoPlayer.tabInfo || {}).length} keys`); - - } catch (error) { - console.error(`Error migrating player ${mongoPlayer.name}:`, error); - skippedCount++; - } - } - - console.log('\n=== Migration Summary ==='); - console.log(`Total players in MongoDB: ${mongoPlayers.length}`); - console.log(`Successfully migrated/updated: ${migratedCount}`); - console.log(`Skipped/failed: ${skippedCount}`); - - // Verify the migration - console.log('\n=== Verification ==='); - const sqlitePlayers = playerHelpers.getAll(); - console.log(`Total players in SQLite: ${sqlitePlayers.length}`); - - // Show sample data - if (sqlitePlayers.length > 0) { - console.log('\nSample migrated player:'); - const sample = sqlitePlayers[0]; - console.log(`Name: ${sample.name}`); - console.log(`Roller info keys: ${Object.keys(sample.rollerInfo).length}`); - console.log(`Shop info keys: ${Object.keys(sample.shopInfo).length}`); - console.log(`Tab info keys: ${Object.keys(sample.tabInfo).length}`); - } - - } catch (error) { - console.error('Migration failed:', error); - } finally { - // Close connections - await mongoose.disconnect(); - console.log('Disconnected from MongoDB'); - } -} - -// Export player data to JSON for backup -async function exportToJson() { - try { - console.log('Exporting MongoDB data to JSON backup...'); - - // Connect to MongoDB - await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true }); - - // Fetch all players - const players = await Player.find(); - - // Create backup object - const backup = { - exportDate: new Date().toISOString(), - playerCount: players.length, - players: players.map(player => ({ - name: player.name, - rollerInfo: player.rollerInfo || {}, - shopInfo: player.shopInfo || {}, - tabInfo: player.tabInfo || {}, - pw: player.pw || '', - pwHash: player.pwHash || '', - mongoId: player._id.toString() - })) - }; - - // Write to file - const fs = require('fs'); - const path = require('path'); - const backupPath = path.join(__dirname, 'mongo-backup.json'); - - fs.writeFileSync(backupPath, JSON.stringify(backup, null, 2)); - console.log(`✓ Backup saved to: ${backupPath}`); - console.log(`✓ Exported ${players.length} players`); - - await mongoose.disconnect(); - - } catch (error) { - console.error('Export failed:', error); - } -} - -// Main function -async function main() { - const args = process.argv.slice(2); - - if (args.includes('--export-only')) { - await exportToJson(); - } else if (args.includes('--migrate-only')) { - await migratePlayers(); - } else { - // Do both - await exportToJson(); - await migratePlayers(); - } - - process.exit(0); -} - -// Run if called directly -if (require.main === module) { - main().catch(console.error); -} - -module.exports = { migratePlayers, exportToJson }; diff --git a/database/mongo-backup.json b/database/mongo-backup.json deleted file mode 100644 index 03e251e..0000000 --- a/database/mongo-backup.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "exportDate": "2025-08-15T07:12:29.894Z", - "playerCount": 3, - "players": [ - { - "name": "christoffer", - "rollerInfo": {}, - "shopInfo": {}, - "tabInfo": { - "rp": 10, - "inventory": [], - "renown": "None" - }, - "pw": "1234", - "pwHash": "1234", - "mongoId": "689e8ba7f8623eb2e1049cea" - }, - { - "name": "claes", - "rollerInfo": {}, - "shopInfo": {}, - "tabInfo": { - "rp": 10, - "inventory": [], - "renown": "None" - }, - "pw": "1234", - "pwHash": "1234", - "mongoId": "689e8ba8f8623eb2e1049cec" - }, - { - "name": "phillip", - "rollerInfo": {}, - "shopInfo": {}, - "tabInfo": { - "rp": 10, - "inventory": [], - "renown": "None" - }, - "pw": "1234", - "pwHash": "1234", - "mongoId": "689e8ba9f8623eb2e1049cee" - } - ] -} \ No newline at end of file diff --git a/database/playerModel.js b/database/playerModel.js deleted file mode 100755 index 7edb283..0000000 --- a/database/playerModel.js +++ /dev/null @@ -1,14 +0,0 @@ -const mongoose = require('mongoose'); - -const playerSchema = new mongoose.Schema({ - name: { type: String, required: true }, - rollerInfo: { type: Object, default: {} }, - shopInfo: { type: Object, default: {} }, - tabInfo: { type: Object, default: {} }, - pw: { type: String, default: '' }, - pwHash: { type: String, default: '' }, -}); - -const Player = mongoose.model('Player', playerSchema); - -module.exports = Player; diff --git a/database/scripts/pm2_smoke_test.sh b/database/scripts/pm2_smoke_test.sh new file mode 100644 index 0000000..9c2ea70 --- /dev/null +++ b/database/scripts/pm2_smoke_test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Start pm2 using the repo pm2.config.js, wait until server is responsive, then stop pm2. +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/.." +# start pm2 using npm script (if available) or call pm2 directly +if command -v npm >/dev/null 2>&1; then + npm run pm2:start || true +else + pm2 start pm2.config.js --env production --update-env || true +fi +# wait for server +for i in {1..15}; do + if curl -sSf http://localhost:5000/ >/dev/null 2>&1; then + echo "server up" + break + fi + sleep 1 +done +# basic health endpoints +curl -sSf http://localhost:5000/api/players || true +curl -sSf http://localhost:5000/api/shop/items || true +# stop pm2 process +if command -v npm >/dev/null 2>&1; then + npm run pm2:stop || true +else + pm2 stop deathwatch-server || true +fi +exit 0 diff --git a/database/server-mongo-backup.js b/database/server-mongo-backup.js deleted file mode 100755 index 12adbd5..0000000 --- a/database/server-mongo-backup.js +++ /dev/null @@ -1,75 +0,0 @@ -require('dotenv').config(); -const express = require('express'); -const cors = require('cors'); -const path = require('path'); -const playerRoutes = require('./routes/playerRoutes-sqlite'); -const sessionRoutes = require('./routes/sessionRoutes-sqlite'); -const shopRoutes = require('./routes/shopRoutes'); -const rulesRoutes = require('./routes/rulesRoutes'); - -const app = express(); -const PORT = process.env.PORT || 5000; - -// Middleware -app.use(express.json()); -app.use(cors()); - -// Serve static files from the React app build directory -app.use(express.static(path.join(__dirname, '../build'))); - -// Initialize SQLite database -const { db } = require('./sqlite-db'); - -// Graceful shutdown -process.on('SIGINT', () => { - console.log('Closing SQLite database...'); - db.close(); - process.exit(0); -}); - -process.on('SIGTERM', () => { - console.log('Closing SQLite database...'); - db.close(); - process.exit(0); -}); - -// Root route for friendly message -app.get('/', (req, res) => { - res.send('Deathwatch Roller API is running with SQLite. Use /api/players for player data.'); -}); - -// Health endpoint checks DB connectivity and players -app.get('/api/health', (req, res) => { - try { - const { playerHelpers } = require('./sqlite-db'); - const players = playerHelpers.getAll(); - res.json({ ok: true, players: players.length, sample: players.slice(0,5).map(p=>p.name) }); - } catch (err) { - console.error('Healthcheck error', err); - res.status(500).json({ ok: false, error: err && err.message ? err.message : String(err) }); - } -}); - -// Use routes -app.use('/api/players', playerRoutes); -app.use('/api/sessions', sessionRoutes); -app.use('/api/shop', shopRoutes); -app.use('/api/rules', rulesRoutes); - -// Catch all handler: send back React's index.html file for any non-API routes -app.get(/^(?!\/api).*/, (req, res) => { - res.sendFile(path.join(__dirname, '../build/index.html')); -}); - -// Start Server -app.listen(PORT, '0.0.0.0', () => { - console.log(`Server running on http://0.0.0.0:${PORT}`); - console.log('Using SQLite database'); -}); - -// Keep the process alive reliably under systemd (avoid accidental exit) -if (process.stdin && typeof process.stdin.resume === 'function') { - process.stdin.resume(); -} - -module.exports = app; diff --git a/database/sqlite/deathwatch.db-shm b/database/sqlite/deathwatch.db-shm index feefc665107b5e7554cb6afd39fa61e6e345e687..00338a40cbb79f5edf9d2cf33101ffdf10cd9715 100644 GIT binary patch delta 693 zcmb7u+KjE?KYpE$HtK#E(p|E^>G;m__1N&8_74ktR)Ue5?CcbYID~6> zDx=mnS(!#a$blt3_i>!X1>8mhp5rY#(2EGVIEHR|>0>HohLPp$5*{4Jb+r81?O%E{ zC4_RUz$WBkACBM*s&NCgXv8zTz&mu}GeC_)7{jrg#6-^ETrQxMVZ=f9Ezflqp#m3C zhx>Sm_vk_&A~}@f7{{4RVVW|tA$wOHj6H!9it%~d|_6Uw* zf_m>2R@vr_LY!7VmYGJSRhknJn&4<)waS|mskIT4;TeycuXhJjIpdCH<3erx4Q5?<@ SoXDy4Fp0^W&vaE$Aie>pq1QkF delta 333 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 OSo{wqe$pdO{C@zBU^xx| delta 97 zcmV~$$rZs+006-Ei6vr*Ep~#4tvMw)Oc@Rea8!U6bfN?^&&N0XKCf^WR8&Q3>KdA2 oEs3^{uAWriz|hE8W@2h)ZeeL=10" } }, - "node_modules/@mongodb-js/saslprep": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", - "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", - "dependencies": { - "sparse-bitfield": "^3.0.3" - } - }, "node_modules/@napi-rs/canvas": { "version": "0.1.77", "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.77.tgz", @@ -5485,19 +5476,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/webidl-conversions": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", - "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" - }, - "node_modules/@types/whatwg-url": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", - "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", - "dependencies": { - "@types/webidl-conversions": "*" - } - }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -7245,14 +7223,6 @@ "node-int64": "^0.4.0" } }, - "node_modules/bson": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", - "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", - "engines": { - "node": ">=16.20.1" - } - }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -15935,14 +15905,6 @@ "node": ">=4.0" } }, - "node_modules/kareem": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", - "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -16408,11 +16370,6 @@ "node": ">= 4.0.0" } }, - "node_modules/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" - }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", @@ -16800,131 +16757,6 @@ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, - "node_modules/mongodb": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz", - "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==", - "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.4", - "mongodb-connection-string-url": "^3.0.0" - }, - "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } - } - }, - "node_modules/mongodb-connection-string-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", - "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", - "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^14.1.0 || ^13.0.0" - } - }, - "node_modules/mongodb-connection-string-url/node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/mongoose": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.17.1.tgz", - "integrity": "sha512-aodS4cacux5caoxB5ErEwRmrafIUsVRJxHnvP7URnSUnTenr32j1qBVV+KjYxryyLSisQkxglAFF69TNLeZTLg==", - "dependencies": { - "bson": "^6.10.4", - "kareem": "2.6.3", - "mongodb": "~6.18.0", - "mpath": "0.9.0", - "mquery": "5.0.0", - "ms": "2.1.3", - "sift": "17.1.3" - }, - "engines": { - "node": ">=16.20.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, - "node_modules/mpath": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", - "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mquery": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", - "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", - "dependencies": { - "debug": "4.x" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -19986,6 +19818,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -21935,11 +21768,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sift": { - "version": "17.1.3", - "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", - "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" - }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -22133,14 +21961,6 @@ "dev": true, "license": "MIT" }, - "node_modules/sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "dependencies": { - "memory-pager": "^1.0.2" - } - }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", diff --git a/package.json b/package.json index 30a7d19..d01ce5e 100755 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ "better-sqlite3": "^12.2.0", "cors": "^2.8.5", "express": "^5.1.0", - "mongoose": "^8.17.1", "pdf-parse": "^1.1.1", "pdfjs-dist": "^4.8.69", "react": "^18.2.0", @@ -28,11 +27,12 @@ "extract:armoury": "node scripts/extract-armoury.js \"$PWD/Deathwatch-Core-Rulebook.pdf\" public/deathwatch-armoury.json", "server": "npm test && cd database && node server.js", "server:notest": "cd database && node server.js", - "db:migrate": "cd database && node migrate-to-sqlite.js", - "db:export": "cd database && node migrate-to-sqlite.js --export-only", "db:sqlite": "cd database && ./switch-db.sh sqlite", - "prestart": "", - "preserver": "" + "pm2:start": "cd database && pm2 start pm2.config.js --env production --update-env", + "pm2:stop": "pm2 stop deathwatch-server || true", + "pm2:reload": "cd database && pm2 reload pm2.config.js --env production --update-env", + "prestart": "", + "preserver": "" }, "eslintConfig": { "extends": [ @@ -66,6 +66,7 @@ "prettier-eslint": "^16.4.2", "react-scripts": "^5.0.1", "tailwindcss": "^3.3.2", - "webpack-dev-server": "^5.2.2" + "webpack-dev-server": "^5.2.2", + "pm2": "^5.2.0" } } diff --git a/scripts/dbinstall.js b/scripts/dbinstall.js deleted file mode 100755 index d5371ba..0000000 --- a/scripts/dbinstall.js +++ /dev/null @@ -1,40 +0,0 @@ -const mongoose = require('mongoose'); -const Player = require('../database/playerModel'); -const GMSession = require('../database/gmSessionModel'); - -async function setupDatabase() { - try { - console.log('Connecting to MongoDB...'); - await mongoose.connect('mongodb://localhost:27017/deathwatch', { - useNewUrlParser: true, - useUnifiedTopology: true, - }); - console.log('Connected to MongoDB.'); - - console.log('Initializing collections...'); - - // Initialize Players collection - const samplePlayers = [ - { name: 'Player1', rollerInfo: {}, shopInfo: {}, tabInfo: {} }, - { name: 'Player2', rollerInfo: {}, shopInfo: {}, tabInfo: {} }, - ]; - await Player.insertMany(samplePlayers); - console.log('Players collection initialized.'); - - // Initialize GM Sessions collection - const sampleSessions = [ - { sessionId: 'session1', isActive: true }, - { sessionId: 'session2', isActive: false }, - ]; - await GMSession.insertMany(sampleSessions); - console.log('GM Sessions collection initialized.'); - - console.log('Database setup completed successfully.'); - } catch (error) { - console.error('Error during database setup:', error.message); - } finally { - mongoose.connection.close(); - } -} - -setupDatabase(); diff --git a/scripts/install-mongodb.js b/scripts/install-mongodb.js deleted file mode 100755 index 4578fca..0000000 --- a/scripts/install-mongodb.js +++ /dev/null @@ -1,26 +0,0 @@ -const { execSync } = require('child_process'); - -function installMongoDB() { - try { - console.log('Installing gnupg and curl...'); - execSync('sudo apt-get install -y gnupg curl', { stdio: 'inherit' }); - - console.log('Importing MongoDB public GPG key...'); - execSync('curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-8.0.gpg --dearmor', { stdio: 'inherit' }); - - console.log('Creating MongoDB list file...'); - execSync('echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list', { stdio: 'inherit' }); - - console.log('Reloading package database...'); - execSync('sudo apt-get update', { stdio: 'inherit' }); - - console.log('Installing MongoDB Community Server...'); - execSync('sudo apt-get install -y mongodb-org', { stdio: 'inherit' }); - - console.log('MongoDB installation completed successfully.'); - } catch (error) { - console.error('Error during MongoDB installation:', error.message); - } -} - -installMongoDB(); diff --git a/src/components/PlayerTab.jsx b/src/components/PlayerTab.jsx index 431a468..b07f9ec 100755 --- a/src/components/PlayerTab.jsx +++ b/src/components/PlayerTab.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useMemo } from 'react'; +import { useState, useEffect, useMemo, useCallback } from 'react'; import axios from 'axios'; import logger, { debug, info, warn, logApiCall, logApiError, logUserAction } from '../utils/logger'; @@ -432,13 +432,9 @@ function PlayerTab({ // Define shopAuthed for use in the component const shopAuthed = getShopAuthed(); - function isGMOrShopAuthed() { - return isGMLoggedIn() || authedPlayer; - } - - function isGMLoggedIn() { - return authedPlayer === 'gm'; - } + // Stable callbacks so hooks depending on them don't change each render + const isGMLoggedIn = useCallback(() => authedPlayer === 'gm', [authedPlayer]); + const isGMOrShopAuthed = useCallback(() => isGMLoggedIn() || !!authedPlayer, [isGMLoggedIn, authedPlayer]); // GM session is now managed in App.js diff --git a/tests/rules.test.js b/tests/rules.test.js new file mode 100644 index 0000000..9bc5285 --- /dev/null +++ b/tests/rules.test.js @@ -0,0 +1,43 @@ +jest.setTimeout(20000); +const { execSync } = require('child_process'); +const path = require('path'); + +describe('Rules endpoints', () => { + const baseURL = process.env.API_BASE || 'http://localhost:5000'; + const gmHeaders = `-H "x-gm-secret: ${process.env.GM_PASSWORD || 'bongo'}"`; + + const curl = (method, url, data = null, headers = '') => { + const command = `curl -s -X ${method} ${headers} -H "Content-Type: application/json" ${data ? `-d '${JSON.stringify(data)}'` : ''} ${url}`; + try { + const result = execSync(command, { encoding: 'utf-8' }); + return JSON.parse(result || '{}'); + } catch (error) { + if (error.stdout) { + try { return JSON.parse(error.stdout); } catch (e) { /* ignore */ } + } + throw error; + } + }; + + test('categories, search, random and stats endpoints respond', () => { + const cats = curl('GET', `${baseURL}/api/rules/categories`); + expect(Array.isArray(cats)).toBe(true); + + const search = curl('GET', `${baseURL}/api/rules/search?q=test`); + expect(Array.isArray(search)).toBe(true); + + const randomRes = curl('GET', `${baseURL}/api/rules/random`); + expect(Array.isArray(randomRes)).toBe(true); + + const stats = curl('GET', `${baseURL}/api/rules/stats`); + expect(stats && typeof stats.totalRules === 'number').toBe(true); + + // Try reload without proper GM secret should fail + const reloadFail = curl('POST', `${baseURL}/api/rules/reload`); + expect(reloadFail && reloadFail.error).toBeTruthy(); + + // Reload with GM secret + const reloadOk = curl('POST', `${baseURL}/api/rules/reload`, null, gmHeaders); + expect(reloadOk && typeof reloadOk.success === 'boolean').toBe(true); + }); +}); diff --git a/tests/shop.test.js b/tests/shop.test.js new file mode 100644 index 0000000..fed69dc --- /dev/null +++ b/tests/shop.test.js @@ -0,0 +1,42 @@ +jest.setTimeout(20000); +const { execSync } = require('child_process'); + +describe('Shop endpoints (public and protected)', () => { + const baseURL = process.env.API_BASE || 'http://localhost:5000'; + const gmHeaders = `-H "x-gm-secret: ${process.env.GM_PASSWORD || 'bongo'}"`; + const mockPlayer = 'shoptestplayer'; + + const curl = (method, url, data = null, headers = '') => { + const command = `curl -s -X ${method} ${headers} -H "Content-Type: application/json" ${data ? `-d '${JSON.stringify(data)}'` : ''} ${url}`; + try { + const result = execSync(command, { encoding: 'utf-8' }); + return JSON.parse(result || '{}'); + } catch (error) { + if (error.stdout) { + try { return JSON.parse(error.stdout); } catch (e) { /* ignore */ } + } + throw error; + } + }; + + test('public items endpoints work and protected inventory requires auth', () => { + const items = curl('GET', `${baseURL}/api/shop/items`); + expect(Array.isArray(items)).toBe(true); + + const category = curl('GET', `${baseURL}/api/shop/items/category/Gear`); + expect(Array.isArray(category)).toBe(true); + + // create player to ensure inventory exists + try { curl('DELETE', `${baseURL}/api/players/${mockPlayer}`, null, gmHeaders); } catch (e) {} + const created = curl('POST', `${baseURL}/api/players`, { name: mockPlayer, rp: 20, pw: 'pw' }, gmHeaders); + expect(created.name).toBe(mockPlayer); + + // Inventory without session should be protected (requireSession returns 403 or similar) + const invNoAuth = curl('GET', `${baseURL}/api/shop/inventory/${mockPlayer}`); + // invNoAuth should be an error object or not an array + expect(Array.isArray(invNoAuth)).toBe(false); + + // cleanup + curl('DELETE', `${baseURL}/api/players/${mockPlayer}`, null, gmHeaders); + }); +});