Implement complete inventory system with equipment database
Features: - HP Control component with damage/heal/direct modes (mobile-optimized) - Conditions system with PF2e condition database - Equipment database with 5,482 items from PF2e (weapons, armor, equipment) - AddItemModal with search, category filters, and pagination - Bulk tracking with encumbered/overburdened status display - Item management (add, remove, toggle equipped) Backend: - Equipment module with search/filter endpoints - Prisma migration for equipment detail fields - Equipment seed script importing from JSON data files - Extended Equipment model (damage, hands, AC, etc.) Frontend: - New components: HpControl, AddConditionModal, AddItemModal - Improved character sheet with tabbed interface - API methods for equipment search and item management Documentation: - CLAUDE.md with project philosophy and architecture decisions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
572
server/package-lock.json
generated
572
server/package-lock.json
generated
@@ -59,6 +59,7 @@
|
||||
"ts-loader": "^9.5.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.20.0"
|
||||
}
|
||||
@@ -258,6 +259,7 @@
|
||||
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/generator": "^7.28.6",
|
||||
@@ -839,7 +841,8 @@
|
||||
"resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.2.tgz",
|
||||
"integrity": "sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@electric-sql/pglite-socket": {
|
||||
"version": "0.0.6",
|
||||
@@ -898,6 +901,448 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
|
||||
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
|
||||
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
|
||||
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
|
||||
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
|
||||
@@ -2279,6 +2724,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.12.tgz",
|
||||
"integrity": "sha512-v6U3O01YohHO+IE3EIFXuRuu3VJILWzyMmSYZXpyBbnp0hk0mFyHxK2w3dF4I5WnbwiRbWlEXdeXFvPQ7qaZzw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"file-type": "21.3.0",
|
||||
"iterare": "1.2.1",
|
||||
@@ -2338,6 +2784,7 @@
|
||||
"integrity": "sha512-97DzTYMf5RtGAVvX1cjwpKRiCUpkeQ9CCzSAenqkAhOmNVVFaApbhuw+xrDt13rsCa2hHVOYPrV4dBgOYMJjsA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@nuxt/opencollective": "0.4.1",
|
||||
"fast-safe-stringify": "2.1.1",
|
||||
@@ -2421,6 +2868,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.12.tgz",
|
||||
"integrity": "sha512-GYK/vHI0SGz5m8mxr7v3Urx8b9t78Cf/dj5aJMZlGd9/1D9OI1hAl00BaphjEXINUJ/BQLxIlF2zUjrYsd6enQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cors": "2.8.5",
|
||||
"express": "5.2.1",
|
||||
@@ -2442,6 +2890,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.12.tgz",
|
||||
"integrity": "sha512-1itTTYsAZecrq2NbJOkch32y8buLwN7UpcNRdJrhlS+ovJ5GxLx3RyJ3KylwBhbYnO5AeYyL1U/i4W5mg/4qDA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"socket.io": "4.8.3",
|
||||
"tslib": "2.8.1"
|
||||
@@ -2620,6 +3069,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.12.tgz",
|
||||
"integrity": "sha512-ulSOYcgosx1TqY425cRC5oXtAu1R10+OSmVfgyR9ueR25k4luekURt8dzAZxhxSCI0OsDj9WKCFLTkEuAwg0wg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"iterare": "1.2.1",
|
||||
"object-hash": "3.0.0",
|
||||
@@ -3092,6 +3542,7 @@
|
||||
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
"@types/json-schema": "*"
|
||||
@@ -3230,6 +3681,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz",
|
||||
"integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
@@ -3411,6 +3863,7 @@
|
||||
"integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.53.0",
|
||||
"@typescript-eslint/types": "8.53.0",
|
||||
@@ -4092,6 +4545,7 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -4141,6 +4595,7 @@
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@@ -4583,6 +5038,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -4844,6 +5300,7 @@
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
@@ -4901,13 +5358,15 @@
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
|
||||
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/class-validator": {
|
||||
"version": "0.14.3",
|
||||
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz",
|
||||
"integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/validator": "^13.15.3",
|
||||
"libphonenumber-js": "^1.11.1",
|
||||
@@ -5261,8 +5720,7 @@
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
@@ -5691,6 +6149,48 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
|
||||
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.27.2",
|
||||
"@esbuild/android-arm": "0.27.2",
|
||||
"@esbuild/android-arm64": "0.27.2",
|
||||
"@esbuild/android-x64": "0.27.2",
|
||||
"@esbuild/darwin-arm64": "0.27.2",
|
||||
"@esbuild/darwin-x64": "0.27.2",
|
||||
"@esbuild/freebsd-arm64": "0.27.2",
|
||||
"@esbuild/freebsd-x64": "0.27.2",
|
||||
"@esbuild/linux-arm": "0.27.2",
|
||||
"@esbuild/linux-arm64": "0.27.2",
|
||||
"@esbuild/linux-ia32": "0.27.2",
|
||||
"@esbuild/linux-loong64": "0.27.2",
|
||||
"@esbuild/linux-mips64el": "0.27.2",
|
||||
"@esbuild/linux-ppc64": "0.27.2",
|
||||
"@esbuild/linux-riscv64": "0.27.2",
|
||||
"@esbuild/linux-s390x": "0.27.2",
|
||||
"@esbuild/linux-x64": "0.27.2",
|
||||
"@esbuild/netbsd-arm64": "0.27.2",
|
||||
"@esbuild/netbsd-x64": "0.27.2",
|
||||
"@esbuild/openbsd-arm64": "0.27.2",
|
||||
"@esbuild/openbsd-x64": "0.27.2",
|
||||
"@esbuild/openharmony-arm64": "0.27.2",
|
||||
"@esbuild/sunos-x64": "0.27.2",
|
||||
"@esbuild/win32-arm64": "0.27.2",
|
||||
"@esbuild/win32-ia32": "0.27.2",
|
||||
"@esbuild/win32-x64": "0.27.2"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
@@ -5726,6 +6226,7 @@
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -5786,6 +6287,7 @@
|
||||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
@@ -6018,6 +6520,7 @@
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.2.1",
|
||||
@@ -6556,6 +7059,19 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
|
||||
"integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/giget": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
|
||||
@@ -6755,6 +7271,7 @@
|
||||
"integrity": "sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
@@ -7141,6 +7658,7 @@
|
||||
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/core": "30.2.0",
|
||||
"@jest/types": "30.2.0",
|
||||
@@ -8939,6 +9457,7 @@
|
||||
"resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
|
||||
"integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"passport-strategy": "1.x.x",
|
||||
"pause": "0.0.1",
|
||||
@@ -9071,6 +9590,7 @@
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.17.1.tgz",
|
||||
"integrity": "sha512-EIR+jXdYNSMOrpRp7g6WgQr7SaZNZfS7IzZIO0oTNEeibq956JxeD15t3Jk3zZH0KH8DmOIx38qJfQenoE8bXQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"pg-connection-string": "^2.10.0",
|
||||
"pg-pool": "^3.11.0",
|
||||
@@ -9354,6 +9874,7 @@
|
||||
"integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -9412,6 +9933,7 @@
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@prisma/config": "7.2.0",
|
||||
"@prisma/dev": "0.17.0",
|
||||
@@ -9622,7 +10144,8 @@
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
||||
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
|
||||
"license": "Apache-2.0"
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/regexp-to-ast": {
|
||||
"version": "0.5.0",
|
||||
@@ -9707,6 +10230,16 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
|
||||
@@ -9759,6 +10292,7 @@
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -9794,8 +10328,7 @@
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "3.3.0",
|
||||
@@ -10504,6 +11037,7 @@
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
@@ -10847,6 +11381,7 @@
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
@@ -10932,6 +11467,26 @@
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.21.0",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.27.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
},
|
||||
"bin": {
|
||||
"tsx": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
@@ -10994,6 +11549,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -11275,6 +11831,7 @@
|
||||
"integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
"@types/estree": "^1.0.8",
|
||||
@@ -11344,6 +11901,7 @@
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
|
||||
@@ -8,8 +8,13 @@
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push",
|
||||
"db:migrate:dev": "prisma migrate dev",
|
||||
"db:migrate:deploy": "prisma migrate deploy",
|
||||
"db:migrate:reset": "prisma migrate reset",
|
||||
"db:migrate:status": "prisma migrate status",
|
||||
"db:studio": "prisma studio",
|
||||
"db:seed": "tsx prisma/seed.ts",
|
||||
"db:seed:equipment": "tsx prisma/seed-equipment.ts",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
@@ -73,6 +78,7 @@
|
||||
"ts-loader": "^9.5.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.20.0"
|
||||
},
|
||||
|
||||
2240
server/prisma/data/armor.json
Normal file
2240
server/prisma/data/armor.json
Normal file
File diff suppressed because it is too large
Load Diff
44780
server/prisma/data/equipment.json
Normal file
44780
server/prisma/data/equipment.json
Normal file
File diff suppressed because it is too large
Load Diff
8786
server/prisma/data/weapons.json
Normal file
8786
server/prisma/data/weapons.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Equipment" ADD COLUMN "ac" INTEGER,
|
||||
ADD COLUMN "armorCategory" TEXT,
|
||||
ADD COLUMN "armorGroup" TEXT,
|
||||
ADD COLUMN "checkPenalty" INTEGER,
|
||||
ADD COLUMN "damageType" TEXT,
|
||||
ADD COLUMN "dexCap" INTEGER,
|
||||
ADD COLUMN "duration" TEXT,
|
||||
ADD COLUMN "reload" TEXT,
|
||||
ADD COLUMN "shieldBt" INTEGER,
|
||||
ADD COLUMN "shieldHardness" INTEGER,
|
||||
ADD COLUMN "shieldHp" INTEGER,
|
||||
ADD COLUMN "speedPenalty" INTEGER,
|
||||
ADD COLUMN "strength" INTEGER,
|
||||
ADD COLUMN "usage" TEXT,
|
||||
ADD COLUMN "weaponGroup" TEXT;
|
||||
@@ -482,18 +482,41 @@ model Equipment {
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
traits String[]
|
||||
itemCategory String
|
||||
itemCategory String // "Weapons", "Armor", "Consumables", "Shields", etc.
|
||||
itemSubcategory String?
|
||||
bulk String?
|
||||
bulk String? // "L" for light, "1", "2", etc.
|
||||
url String?
|
||||
summary String?
|
||||
activation String?
|
||||
hands String?
|
||||
damage String?
|
||||
range String?
|
||||
weaponCategory String?
|
||||
price Int? // In CP
|
||||
level Int?
|
||||
price Int? // In CP
|
||||
|
||||
// Weapon-specific fields
|
||||
hands String?
|
||||
damage String? // "1d8 S", "1d6 P", etc.
|
||||
damageType String? // "S", "P", "B" (Slashing, Piercing, Bludgeoning)
|
||||
range String?
|
||||
reload String?
|
||||
weaponCategory String? // "Simple", "Martial", "Advanced", "Ammunition"
|
||||
weaponGroup String? // "Sword", "Axe", "Bow", etc.
|
||||
|
||||
// Armor-specific fields
|
||||
ac Int?
|
||||
dexCap Int?
|
||||
checkPenalty Int?
|
||||
speedPenalty Int?
|
||||
strength Int? // Strength requirement
|
||||
armorCategory String? // "Unarmored", "Light", "Medium", "Heavy"
|
||||
armorGroup String? // "Leather", "Chain", "Plate", etc.
|
||||
|
||||
// Shield-specific fields
|
||||
shieldHp Int?
|
||||
shieldHardness Int?
|
||||
shieldBt Int? // Broken Threshold
|
||||
|
||||
// Consumable/Equipment-specific fields
|
||||
activation String? // "Cast A Spell", "[one-action]", etc.
|
||||
duration String?
|
||||
usage String?
|
||||
|
||||
characterItems CharacterItem[]
|
||||
}
|
||||
|
||||
254
server/prisma/seed-equipment.ts
Normal file
254
server/prisma/seed-equipment.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
import 'dotenv/config';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { PrismaClient } from '../src/generated/prisma/client.js';
|
||||
import { PrismaPg } from '@prisma/adapter-pg';
|
||||
|
||||
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
interface WeaponJson {
|
||||
name: string;
|
||||
trait: string;
|
||||
item_category: string;
|
||||
item_subcategory: string;
|
||||
bulk: string;
|
||||
url: string;
|
||||
summary: string;
|
||||
hands?: string;
|
||||
damage?: string;
|
||||
range?: string;
|
||||
weapon_category?: string;
|
||||
}
|
||||
|
||||
interface ArmorJson {
|
||||
name: string;
|
||||
trait: string;
|
||||
item_category: string;
|
||||
item_subcategory: string;
|
||||
bulk: string;
|
||||
url: string;
|
||||
summary: string;
|
||||
ac?: string;
|
||||
dex_cap?: string;
|
||||
}
|
||||
|
||||
interface EquipmentJson {
|
||||
name: string;
|
||||
trait: string;
|
||||
item_category: string;
|
||||
item_subcategory: string;
|
||||
bulk: string;
|
||||
url: string;
|
||||
summary: string;
|
||||
activation?: string;
|
||||
}
|
||||
|
||||
function parseTraits(traitString: string): string[] {
|
||||
if (!traitString || traitString.trim() === '') return [];
|
||||
return traitString.split(',').map(t => t.trim()).filter(t => t.length > 0);
|
||||
}
|
||||
|
||||
function parseDamage(damageStr: string): { damage: string | null; damageType: string | null } {
|
||||
if (!damageStr || damageStr.trim() === '') return { damage: null, damageType: null };
|
||||
|
||||
// Parse strings like "1d8 S", "1d6 P", "2d6 B"
|
||||
const match = damageStr.match(/^(.+?)\s+([SPB])$/i);
|
||||
if (match) {
|
||||
return { damage: match[1].trim(), damageType: match[2].toUpperCase() };
|
||||
}
|
||||
return { damage: damageStr, damageType: null };
|
||||
}
|
||||
|
||||
function parseNumber(str: string | undefined): number | null {
|
||||
if (!str || str.trim() === '') return null;
|
||||
const num = parseInt(str, 10);
|
||||
return isNaN(num) ? null : num;
|
||||
}
|
||||
|
||||
async function seedWeapons() {
|
||||
const dataPath = path.join(__dirname, 'data', 'weapons.json');
|
||||
const data: WeaponJson[] = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
|
||||
|
||||
console.log(`⚔️ Importing ${data.length} weapons...`);
|
||||
|
||||
let created = 0;
|
||||
let updated = 0;
|
||||
let errors = 0;
|
||||
|
||||
for (const item of data) {
|
||||
try {
|
||||
const { damage, damageType } = parseDamage(item.damage || '');
|
||||
|
||||
// Check if exists first
|
||||
const existing = await prisma.equipment.findUnique({ where: { name: item.name } });
|
||||
|
||||
if (existing) {
|
||||
// Update with weapon-specific fields
|
||||
await prisma.equipment.update({
|
||||
where: { name: item.name },
|
||||
data: {
|
||||
hands: item.hands || existing.hands,
|
||||
damage: damage || existing.damage,
|
||||
damageType: damageType || existing.damageType,
|
||||
range: item.range || existing.range,
|
||||
weaponCategory: item.weapon_category || existing.weaponCategory,
|
||||
// Don't overwrite traits/summary if already set
|
||||
traits: existing.traits.length > 0 ? existing.traits : parseTraits(item.trait),
|
||||
summary: existing.summary || item.summary || null,
|
||||
},
|
||||
});
|
||||
updated++;
|
||||
} else {
|
||||
await prisma.equipment.create({
|
||||
data: {
|
||||
name: item.name,
|
||||
traits: parseTraits(item.trait),
|
||||
itemCategory: item.item_category || 'Weapons',
|
||||
itemSubcategory: item.item_subcategory || null,
|
||||
bulk: item.bulk || null,
|
||||
url: item.url || null,
|
||||
summary: item.summary || null,
|
||||
hands: item.hands || null,
|
||||
damage,
|
||||
damageType,
|
||||
range: item.range || null,
|
||||
weaponCategory: item.weapon_category || null,
|
||||
},
|
||||
});
|
||||
created++;
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (errors === 0) {
|
||||
// Print full error for first failure only
|
||||
console.log(` ⚠️ First error for "${item.name}":`);
|
||||
console.log(error.message);
|
||||
}
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ Created: ${created}, Updated: ${updated}, Errors: ${errors}`);
|
||||
}
|
||||
|
||||
async function seedArmor() {
|
||||
const dataPath = path.join(__dirname, 'data', 'armor.json');
|
||||
const data: ArmorJson[] = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
|
||||
|
||||
console.log(`🛡️ Importing ${data.length} armor items...`);
|
||||
|
||||
let created = 0;
|
||||
let updated = 0;
|
||||
let errors = 0;
|
||||
|
||||
for (const item of data) {
|
||||
try {
|
||||
const existing = await prisma.equipment.findUnique({ where: { name: item.name } });
|
||||
|
||||
if (existing) {
|
||||
// Update with armor-specific fields
|
||||
await prisma.equipment.update({
|
||||
where: { name: item.name },
|
||||
data: {
|
||||
ac: parseNumber(item.ac) ?? existing.ac,
|
||||
dexCap: parseNumber(item.dex_cap) ?? existing.dexCap,
|
||||
traits: existing.traits.length > 0 ? existing.traits : parseTraits(item.trait),
|
||||
summary: existing.summary || item.summary || null,
|
||||
},
|
||||
});
|
||||
updated++;
|
||||
} else {
|
||||
await prisma.equipment.create({
|
||||
data: {
|
||||
name: item.name,
|
||||
traits: parseTraits(item.trait),
|
||||
itemCategory: item.item_category || 'Armor',
|
||||
itemSubcategory: item.item_subcategory || null,
|
||||
bulk: item.bulk || null,
|
||||
url: item.url || null,
|
||||
summary: item.summary || null,
|
||||
ac: parseNumber(item.ac),
|
||||
dexCap: parseNumber(item.dex_cap),
|
||||
},
|
||||
});
|
||||
created++;
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (errors < 3) {
|
||||
console.log(` ⚠️ Error for "${item.name}": ${error.message?.slice(0, 100)}`);
|
||||
}
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ Created: ${created}, Updated: ${updated}, Errors: ${errors}`);
|
||||
}
|
||||
|
||||
async function seedEquipment() {
|
||||
const dataPath = path.join(__dirname, 'data', 'equipment.json');
|
||||
const data: EquipmentJson[] = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
|
||||
|
||||
console.log(`📦 Importing ${data.length} equipment items...`);
|
||||
|
||||
let created = 0;
|
||||
let updated = 0;
|
||||
let errors = 0;
|
||||
|
||||
for (const item of data) {
|
||||
try {
|
||||
await prisma.equipment.upsert({
|
||||
where: { name: item.name },
|
||||
update: {
|
||||
traits: parseTraits(item.trait),
|
||||
itemCategory: item.item_category || 'Equipment',
|
||||
itemSubcategory: item.item_subcategory || null,
|
||||
bulk: item.bulk || null,
|
||||
url: item.url || null,
|
||||
summary: item.summary || null,
|
||||
activation: item.activation || null,
|
||||
},
|
||||
create: {
|
||||
name: item.name,
|
||||
traits: parseTraits(item.trait),
|
||||
itemCategory: item.item_category || 'Equipment',
|
||||
itemSubcategory: item.item_subcategory || null,
|
||||
bulk: item.bulk || null,
|
||||
url: item.url || null,
|
||||
summary: item.summary || null,
|
||||
activation: item.activation || null,
|
||||
},
|
||||
});
|
||||
created++;
|
||||
} catch (error) {
|
||||
// Item with same name already exists - count as update attempt
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ Created: ${created}, Duplicates: ${updated}, Errors: ${errors}`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🗃️ Seeding Pathfinder 2e Equipment Database...\n');
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
// WICHTIG: Equipment zuerst, dann Waffen/Rüstung um spezifische Felder zu ergänzen
|
||||
await seedEquipment();
|
||||
await seedWeapons(); // Ergänzt damage, hands, weapon_category etc.
|
||||
await seedArmor(); // Ergänzt ac, dex_cap etc.
|
||||
|
||||
const totalCount = await prisma.equipment.count();
|
||||
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
|
||||
console.log(`\n✅ Equipment database seeded successfully!`);
|
||||
console.log(` Total items in database: ${totalCount}`);
|
||||
console.log(` Duration: ${duration}s`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error('Equipment seeding failed:', e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(() => prisma.$disconnect());
|
||||
@@ -7,9 +7,13 @@ const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
async function main() {
|
||||
const passwordHash = await bcrypt.hash('admin123', 10);
|
||||
console.log('Seeding database...\n');
|
||||
|
||||
const user = await prisma.user.upsert({
|
||||
// Create password hash
|
||||
const passwordHash = await bcrypt.hash('password123', 10);
|
||||
|
||||
// Create Admin User
|
||||
const admin = await prisma.user.upsert({
|
||||
where: { email: 'admin@dimension47.local' },
|
||||
update: {},
|
||||
create: {
|
||||
@@ -19,10 +23,208 @@ async function main() {
|
||||
role: 'ADMIN',
|
||||
},
|
||||
});
|
||||
console.log('Created Admin:', admin.username);
|
||||
|
||||
console.log('Admin user created:', user.username, user.email);
|
||||
// Create GM User
|
||||
const gm = await prisma.user.upsert({
|
||||
where: { email: 'gm@dimension47.local' },
|
||||
update: {},
|
||||
create: {
|
||||
username: 'gamemaster',
|
||||
email: 'gm@dimension47.local',
|
||||
passwordHash,
|
||||
role: 'GM',
|
||||
},
|
||||
});
|
||||
console.log('Created GM:', gm.username);
|
||||
|
||||
// Create Player Users
|
||||
const player1 = await prisma.user.upsert({
|
||||
where: { email: 'player1@dimension47.local' },
|
||||
update: {},
|
||||
create: {
|
||||
username: 'spieler1',
|
||||
email: 'player1@dimension47.local',
|
||||
passwordHash,
|
||||
role: 'PLAYER',
|
||||
},
|
||||
});
|
||||
console.log('Created Player:', player1.username);
|
||||
|
||||
const player2 = await prisma.user.upsert({
|
||||
where: { email: 'player2@dimension47.local' },
|
||||
update: {},
|
||||
create: {
|
||||
username: 'spieler2',
|
||||
email: 'player2@dimension47.local',
|
||||
passwordHash,
|
||||
role: 'PLAYER',
|
||||
},
|
||||
});
|
||||
console.log('Created Player:', player2.username);
|
||||
|
||||
// Create Test Campaign
|
||||
const campaign = await prisma.campaign.upsert({
|
||||
where: { id: '00000000-0000-0000-0000-000000000001' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '00000000-0000-0000-0000-000000000001',
|
||||
name: 'Abendliche Schatten',
|
||||
description: 'Eine spannende Kampagne in der Welt von Golarion. Die Helden erkunden uralte Ruinen und stellen sich finsteren Mächten.',
|
||||
gmId: gm.id,
|
||||
},
|
||||
});
|
||||
console.log('Created Campaign:', campaign.name);
|
||||
|
||||
// Add members to campaign
|
||||
await prisma.campaignMember.upsert({
|
||||
where: { campaignId_userId: { campaignId: campaign.id, userId: gm.id } },
|
||||
update: {},
|
||||
create: { campaignId: campaign.id, userId: gm.id },
|
||||
});
|
||||
|
||||
await prisma.campaignMember.upsert({
|
||||
where: { campaignId_userId: { campaignId: campaign.id, userId: player1.id } },
|
||||
update: {},
|
||||
create: { campaignId: campaign.id, userId: player1.id },
|
||||
});
|
||||
|
||||
await prisma.campaignMember.upsert({
|
||||
where: { campaignId_userId: { campaignId: campaign.id, userId: player2.id } },
|
||||
update: {},
|
||||
create: { campaignId: campaign.id, userId: player2.id },
|
||||
});
|
||||
console.log('Added members to campaign');
|
||||
|
||||
// Create Test Characters
|
||||
const character1 = await prisma.character.upsert({
|
||||
where: { id: '00000000-0000-0000-0000-000000000101' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '00000000-0000-0000-0000-000000000101',
|
||||
campaignId: campaign.id,
|
||||
ownerId: player1.id,
|
||||
name: 'Thorin Eisenschild',
|
||||
type: 'PC',
|
||||
level: 3,
|
||||
hpCurrent: 38,
|
||||
hpMax: 42,
|
||||
hpTemp: 0,
|
||||
ancestryId: 'dwarf',
|
||||
classId: 'fighter',
|
||||
backgroundId: 'warrior',
|
||||
experiencePoints: 1200,
|
||||
},
|
||||
});
|
||||
console.log('Created Character:', character1.name);
|
||||
|
||||
// Add abilities for character1
|
||||
const abilities1 = [
|
||||
{ ability: 'STR' as const, score: 18 },
|
||||
{ ability: 'DEX' as const, score: 12 },
|
||||
{ ability: 'CON' as const, score: 16 },
|
||||
{ ability: 'INT' as const, score: 10 },
|
||||
{ ability: 'WIS' as const, score: 14 },
|
||||
{ ability: 'CHA' as const, score: 8 },
|
||||
];
|
||||
|
||||
for (const ab of abilities1) {
|
||||
await prisma.characterAbility.upsert({
|
||||
where: { characterId_ability: { characterId: character1.id, ability: ab.ability } },
|
||||
update: { score: ab.score },
|
||||
create: { characterId: character1.id, ability: ab.ability, score: ab.score },
|
||||
});
|
||||
}
|
||||
console.log('Added abilities to', character1.name);
|
||||
|
||||
const character2 = await prisma.character.upsert({
|
||||
where: { id: '00000000-0000-0000-0000-000000000102' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '00000000-0000-0000-0000-000000000102',
|
||||
campaignId: campaign.id,
|
||||
ownerId: player2.id,
|
||||
name: 'Elara Sternenlicht',
|
||||
type: 'PC',
|
||||
level: 3,
|
||||
hpCurrent: 24,
|
||||
hpMax: 28,
|
||||
hpTemp: 0,
|
||||
ancestryId: 'elf',
|
||||
classId: 'wizard',
|
||||
backgroundId: 'scholar',
|
||||
experiencePoints: 1200,
|
||||
},
|
||||
});
|
||||
console.log('Created Character:', character2.name);
|
||||
|
||||
// Add abilities for character2
|
||||
const abilities2 = [
|
||||
{ ability: 'STR' as const, score: 8 },
|
||||
{ ability: 'DEX' as const, score: 14 },
|
||||
{ ability: 'CON' as const, score: 12 },
|
||||
{ ability: 'INT' as const, score: 18 },
|
||||
{ ability: 'WIS' as const, score: 14 },
|
||||
{ ability: 'CHA' as const, score: 12 },
|
||||
];
|
||||
|
||||
for (const ab of abilities2) {
|
||||
await prisma.characterAbility.upsert({
|
||||
where: { characterId_ability: { characterId: character2.id, ability: ab.ability } },
|
||||
update: { score: ab.score },
|
||||
create: { characterId: character2.id, ability: ab.ability, score: ab.score },
|
||||
});
|
||||
}
|
||||
console.log('Added abilities to', character2.name);
|
||||
|
||||
// Create an NPC
|
||||
const npc = await prisma.character.upsert({
|
||||
where: { id: '00000000-0000-0000-0000-000000000201' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '00000000-0000-0000-0000-000000000201',
|
||||
campaignId: campaign.id,
|
||||
ownerId: null,
|
||||
name: 'Meister Aldric',
|
||||
type: 'NPC',
|
||||
level: 5,
|
||||
hpCurrent: 55,
|
||||
hpMax: 55,
|
||||
hpTemp: 0,
|
||||
},
|
||||
});
|
||||
console.log('Created NPC:', npc.name);
|
||||
|
||||
// Create a second campaign
|
||||
const campaign2 = await prisma.campaign.upsert({
|
||||
where: { id: '00000000-0000-0000-0000-000000000002' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '00000000-0000-0000-0000-000000000002',
|
||||
name: 'Die verlorene Stadt',
|
||||
description: 'Eine Expedition in die legendäre verlorene Stadt Xin-Shalast.',
|
||||
gmId: gm.id,
|
||||
},
|
||||
});
|
||||
console.log('Created Campaign:', campaign2.name);
|
||||
|
||||
await prisma.campaignMember.upsert({
|
||||
where: { campaignId_userId: { campaignId: campaign2.id, userId: gm.id } },
|
||||
update: {},
|
||||
create: { campaignId: campaign2.id, userId: gm.id },
|
||||
});
|
||||
|
||||
console.log('\n✅ Database seeded successfully!');
|
||||
console.log('\n📋 Test Accounts:');
|
||||
console.log(' Admin: admin@dimension47.local / password123');
|
||||
console.log(' GM: gm@dimension47.local / password123');
|
||||
console.log(' Player 1: player1@dimension47.local / password123');
|
||||
console.log(' Player 2: player2@dimension47.local / password123');
|
||||
}
|
||||
|
||||
main()
|
||||
.catch(console.error)
|
||||
.catch((e) => {
|
||||
console.error('Seeding failed:', e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(() => prisma.$disconnect());
|
||||
|
||||
@@ -11,6 +11,7 @@ import { AuthModule } from './modules/auth/auth.module';
|
||||
import { CampaignsModule } from './modules/campaigns/campaigns.module';
|
||||
import { CharactersModule } from './modules/characters/characters.module';
|
||||
import { TranslationsModule } from './modules/translations/translations.module';
|
||||
import { EquipmentModule } from './modules/equipment/equipment.module';
|
||||
|
||||
// Guards
|
||||
import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard';
|
||||
@@ -33,6 +34,7 @@ import { RolesGuard } from './modules/auth/guards/roles.guard';
|
||||
CampaignsModule,
|
||||
CharactersModule,
|
||||
TranslationsModule,
|
||||
EquipmentModule,
|
||||
],
|
||||
providers: [
|
||||
// Global JWT Auth Guard
|
||||
|
||||
128
server/src/modules/equipment/equipment.controller.ts
Normal file
128
server/src/modules/equipment/equipment.controller.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiQuery, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard.js';
|
||||
import { EquipmentService } from './equipment.service.js';
|
||||
|
||||
@ApiTags('Equipment')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('equipment')
|
||||
export class EquipmentController {
|
||||
constructor(private readonly equipmentService: EquipmentService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Search and browse equipment' })
|
||||
@ApiQuery({ name: 'query', required: false, description: 'Search term for name' })
|
||||
@ApiQuery({ name: 'category', required: false, description: 'Filter by category (Weapons, Armor, Consumables, etc.)' })
|
||||
@ApiQuery({ name: 'subcategory', required: false, description: 'Filter by subcategory' })
|
||||
@ApiQuery({ name: 'minLevel', required: false, type: Number })
|
||||
@ApiQuery({ name: 'maxLevel', required: false, type: Number })
|
||||
@ApiQuery({ name: 'traits', required: false, description: 'Comma-separated list of traits' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number, description: 'Page number (default: 1)' })
|
||||
@ApiQuery({ name: 'limit', required: false, type: Number, description: 'Items per page (default: 50)' })
|
||||
async search(
|
||||
@Query('query') query?: string,
|
||||
@Query('category') category?: string,
|
||||
@Query('subcategory') subcategory?: string,
|
||||
@Query('minLevel') minLevel?: string,
|
||||
@Query('maxLevel') maxLevel?: string,
|
||||
@Query('traits') traits?: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('limit') limit?: string,
|
||||
) {
|
||||
return this.equipmentService.search({
|
||||
query,
|
||||
category,
|
||||
subcategory,
|
||||
minLevel: minLevel ? parseInt(minLevel, 10) : undefined,
|
||||
maxLevel: maxLevel ? parseInt(maxLevel, 10) : undefined,
|
||||
traits: traits ? traits.split(',').map(t => t.trim()) : undefined,
|
||||
page: page ? parseInt(page, 10) : 1,
|
||||
limit: limit ? parseInt(limit, 10) : 50,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('categories')
|
||||
@ApiOperation({ summary: 'Get all equipment categories' })
|
||||
async getCategories() {
|
||||
return this.equipmentService.getCategories();
|
||||
}
|
||||
|
||||
@Get('categories/:category/subcategories')
|
||||
@ApiOperation({ summary: 'Get subcategories for a category' })
|
||||
async getSubcategories(@Param('category') category: string) {
|
||||
return this.equipmentService.getSubcategories(category);
|
||||
}
|
||||
|
||||
@Get('weapons')
|
||||
@ApiOperation({ summary: 'Browse weapons' })
|
||||
@ApiQuery({ name: 'query', required: false })
|
||||
@ApiQuery({ name: 'subcategory', required: false })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'limit', required: false, type: Number })
|
||||
async getWeapons(
|
||||
@Query('query') query?: string,
|
||||
@Query('subcategory') subcategory?: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('limit') limit?: string,
|
||||
) {
|
||||
return this.equipmentService.getWeapons({
|
||||
query,
|
||||
subcategory,
|
||||
page: page ? parseInt(page, 10) : 1,
|
||||
limit: limit ? parseInt(limit, 10) : 50,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('armor')
|
||||
@ApiOperation({ summary: 'Browse armor' })
|
||||
@ApiQuery({ name: 'query', required: false })
|
||||
@ApiQuery({ name: 'subcategory', required: false })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'limit', required: false, type: Number })
|
||||
async getArmor(
|
||||
@Query('query') query?: string,
|
||||
@Query('subcategory') subcategory?: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('limit') limit?: string,
|
||||
) {
|
||||
return this.equipmentService.getArmor({
|
||||
query,
|
||||
subcategory,
|
||||
page: page ? parseInt(page, 10) : 1,
|
||||
limit: limit ? parseInt(limit, 10) : 50,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('consumables')
|
||||
@ApiOperation({ summary: 'Browse consumables' })
|
||||
@ApiQuery({ name: 'query', required: false })
|
||||
@ApiQuery({ name: 'subcategory', required: false })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'limit', required: false, type: Number })
|
||||
async getConsumables(
|
||||
@Query('query') query?: string,
|
||||
@Query('subcategory') subcategory?: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('limit') limit?: string,
|
||||
) {
|
||||
return this.equipmentService.getConsumables({
|
||||
query,
|
||||
subcategory,
|
||||
page: page ? parseInt(page, 10) : 1,
|
||||
limit: limit ? parseInt(limit, 10) : 50,
|
||||
});
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get equipment by ID' })
|
||||
async getById(@Param('id') id: string) {
|
||||
return this.equipmentService.getById(id);
|
||||
}
|
||||
|
||||
@Get('by-name/:name')
|
||||
@ApiOperation({ summary: 'Get equipment by exact name' })
|
||||
async getByName(@Param('name') name: string) {
|
||||
return this.equipmentService.getByName(decodeURIComponent(name));
|
||||
}
|
||||
}
|
||||
12
server/src/modules/equipment/equipment.module.ts
Normal file
12
server/src/modules/equipment/equipment.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { EquipmentController } from './equipment.controller.js';
|
||||
import { EquipmentService } from './equipment.service.js';
|
||||
import { PrismaModule } from '../../prisma/prisma.module.js';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [EquipmentController],
|
||||
providers: [EquipmentService],
|
||||
exports: [EquipmentService],
|
||||
})
|
||||
export class EquipmentModule {}
|
||||
153
server/src/modules/equipment/equipment.service.ts
Normal file
153
server/src/modules/equipment/equipment.service.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../../prisma/prisma.service.js';
|
||||
|
||||
export interface EquipmentSearchParams {
|
||||
query?: string;
|
||||
category?: string;
|
||||
subcategory?: string;
|
||||
minLevel?: number;
|
||||
maxLevel?: number;
|
||||
traits?: string[];
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface EquipmentSearchResult {
|
||||
items: any[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
categories: string[];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class EquipmentService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async search(params: EquipmentSearchParams): Promise<EquipmentSearchResult> {
|
||||
const {
|
||||
query,
|
||||
category,
|
||||
subcategory,
|
||||
minLevel,
|
||||
maxLevel,
|
||||
traits,
|
||||
page = 1,
|
||||
limit = 50,
|
||||
} = params;
|
||||
|
||||
// Build where clause
|
||||
const where: any = {};
|
||||
|
||||
// Text search on name
|
||||
if (query && query.trim()) {
|
||||
where.name = {
|
||||
contains: query.trim(),
|
||||
mode: 'insensitive',
|
||||
};
|
||||
}
|
||||
|
||||
// Category filter
|
||||
if (category) {
|
||||
where.itemCategory = category;
|
||||
}
|
||||
|
||||
// Subcategory filter
|
||||
if (subcategory) {
|
||||
where.itemSubcategory = subcategory;
|
||||
}
|
||||
|
||||
// Level range
|
||||
if (minLevel !== undefined || maxLevel !== undefined) {
|
||||
where.level = {};
|
||||
if (minLevel !== undefined) {
|
||||
where.level.gte = minLevel;
|
||||
}
|
||||
if (maxLevel !== undefined) {
|
||||
where.level.lte = maxLevel;
|
||||
}
|
||||
}
|
||||
|
||||
// Traits filter (has any of the specified traits)
|
||||
if (traits && traits.length > 0) {
|
||||
where.traits = {
|
||||
hasSome: traits,
|
||||
};
|
||||
}
|
||||
|
||||
// Get total count
|
||||
const total = await this.prisma.equipment.count({ where });
|
||||
|
||||
// Get paginated results
|
||||
const items = await this.prisma.equipment.findMany({
|
||||
where,
|
||||
orderBy: [
|
||||
{ level: 'asc' },
|
||||
{ name: 'asc' },
|
||||
],
|
||||
skip: (page - 1) * limit,
|
||||
take: limit,
|
||||
});
|
||||
|
||||
// Get all unique categories for filter UI
|
||||
const categoriesResult = await this.prisma.equipment.groupBy({
|
||||
by: ['itemCategory'],
|
||||
orderBy: { itemCategory: 'asc' },
|
||||
});
|
||||
const categories = categoriesResult.map(c => c.itemCategory);
|
||||
|
||||
return {
|
||||
items,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
categories,
|
||||
};
|
||||
}
|
||||
|
||||
async getById(id: string) {
|
||||
return this.prisma.equipment.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
|
||||
async getByName(name: string) {
|
||||
return this.prisma.equipment.findUnique({
|
||||
where: { name },
|
||||
});
|
||||
}
|
||||
|
||||
async getCategories(): Promise<string[]> {
|
||||
const result = await this.prisma.equipment.groupBy({
|
||||
by: ['itemCategory'],
|
||||
orderBy: { itemCategory: 'asc' },
|
||||
});
|
||||
return result.map(c => c.itemCategory);
|
||||
}
|
||||
|
||||
async getSubcategories(category: string): Promise<string[]> {
|
||||
const result = await this.prisma.equipment.groupBy({
|
||||
by: ['itemSubcategory'],
|
||||
where: {
|
||||
itemCategory: category,
|
||||
itemSubcategory: { not: null },
|
||||
},
|
||||
orderBy: { itemSubcategory: 'asc' },
|
||||
});
|
||||
return result.map(c => c.itemSubcategory).filter((s): s is string => s !== null);
|
||||
}
|
||||
|
||||
async getWeapons(params: Omit<EquipmentSearchParams, 'category'>) {
|
||||
return this.search({ ...params, category: 'Weapons' });
|
||||
}
|
||||
|
||||
async getArmor(params: Omit<EquipmentSearchParams, 'category'>) {
|
||||
return this.search({ ...params, category: 'Armor' });
|
||||
}
|
||||
|
||||
async getConsumables(params: Omit<EquipmentSearchParams, 'category'>) {
|
||||
return this.search({ ...params, category: 'Consumables' });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user