add: Proper i18n.

fix: Migrations, Page names.
This commit is contained in:
WildEgo 2025-06-05 23:56:02 +01:00
parent fe18ec099a
commit fe465982ad
44 changed files with 5212 additions and 17187 deletions

12
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
"configurations": [
{
"type": "cmake",
"request": "launch",
"name": "Debug portfile(s)",
"cmakeDebugType": "external",
"pipeName": "/tmp/vcpkg_ext_portfile_dbg",
"preLaunchTask": "Debug vcpkg commands"
}
]
}

View File

@ -35,8 +35,8 @@
"@radix-ui/react-tooltip": "^1.2.7",
"@react-email/components": "0.0.41",
"@tailwindcss/postcss": "^4.1.8",
"@tanstack/react-query": "^5.80.3",
"@tanstack/react-query-devtools": "^5.80.3",
"@tanstack/react-query": "^5.80.6",
"@tanstack/react-query-devtools": "^5.80.6",
"@tanstack/react-router": "^1.120.15",
"@tanstack/react-router-devtools": "^1.120.15",
"@tanstack/react-start": "^1.120.15",
@ -68,7 +68,7 @@
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@inlang/cli": "^3.0.11",
"@inlang/paraglide-js": "^2.0.13",
"@inlang/paraglide-js": "2.0.13",
"@tanstack/eslint-plugin-query": "^5.78.0",
"@types/bun": "^1.2.15",
"@types/nodemailer": "^6.4.17",
@ -691,29 +691,29 @@
"@tanstack/history": ["@tanstack/history@1.115.0", "", {}, "sha512-K7JJNrRVvyjAVnbXOH2XLRhFXDkeP54Kt2P4FR1Kl2KDGlIbkua5VqZQD2rot3qaDrpufyUa63nuLai1kOLTsQ=="],
"@tanstack/query-core": ["@tanstack/query-core@5.80.2", "", {}, "sha512-g2Es97uwFk7omkWiH9JmtLWSA8lTUFVseIyzqbjqJEEx7qN+Hg6jbBdDvelqtakamppaJtGORQ64hEJ5S6ojSg=="],
"@tanstack/query-core": ["@tanstack/query-core@5.80.6", "", {}, "sha512-nl7YxT/TAU+VTf+e2zTkObGTyY8YZBMnbgeA1ee66lIVqzKlYursAII6z5t0e6rXgwUMJSV4dshBTNacNpZHbQ=="],
"@tanstack/query-devtools": ["@tanstack/query-devtools@5.80.0", "", {}, "sha512-D6gH4asyjaoXrCOt5vG5Og/YSj0D/TxwNQgtLJIgWbhbWCC/emu2E92EFoVHh4ppVWg1qT2gKHvKyQBEFZhCuA=="],
"@tanstack/react-query": ["@tanstack/react-query@5.80.3", "", { "dependencies": { "@tanstack/query-core": "5.80.2" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-psqr/QRzYfqJvgD8F2teMO6mL4hN4gzkOra9BlPplNhwByviZIhHUrWTXQEMmUdPWHNkGjA1SP6xG2+brhmIoQ=="],
"@tanstack/react-query": ["@tanstack/react-query@5.80.6", "", { "dependencies": { "@tanstack/query-core": "5.80.6" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-izX+5CnkpON3NQGcEm3/d7LfFQNo9ZpFtX2QsINgCYK9LT2VCIdi8D3bMaMSNhrAJCznRoAkFic76uvLroALBw=="],
"@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.80.3", "", { "dependencies": { "@tanstack/query-devtools": "5.80.0" }, "peerDependencies": { "@tanstack/react-query": "^5.80.3", "react": "^18 || ^19" } }, "sha512-WfoTdSd/SvBL7BJQzr2iQ8XGhMTw9hnKQn96ztG53Hm3AzWyvDrG8FoAPpwIE6c/f9+kmFGCxMvvTVueAy+0Gw=="],
"@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.80.6", "", { "dependencies": { "@tanstack/query-devtools": "5.80.0" }, "peerDependencies": { "@tanstack/react-query": "^5.80.6", "react": "^18 || ^19" } }, "sha512-y7Es0OJ4RYQxrPYsuuQP0jxjgJ40a03UbEPmJ6vwf/ERVMRoRIMkpjtvPxf1D+n9nwPfWmGdD0jW8Wxd+TxeEw=="],
"@tanstack/react-router": ["@tanstack/react-router@1.120.15", "", { "dependencies": { "@tanstack/history": "1.115.0", "@tanstack/react-store": "^0.7.0", "@tanstack/router-core": "1.120.15", "jsesc": "^3.1.0", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-apzBmXh4pHwqUGU3kD8y2FJMi7rVoUbRxh5oV7v8kEb6Aq5Xpdo+OcpThw8h/M2zv7v4Ef8IoY6WFCKKu3HBjQ=="],
"@tanstack/react-router": ["@tanstack/react-router@1.120.16", "", { "dependencies": { "@tanstack/history": "1.115.0", "@tanstack/react-store": "^0.7.0", "@tanstack/router-core": "1.120.15", "jsesc": "^3.1.0", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-bBZ+H9sBYcihsj1BkcxD/VtVxa5ZGmCEeYXlCAgWQ9fWc1kN+tA0/M2uvjLFuhsESDmv5U45TittBtHAwAgEAA=="],
"@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.120.15", "", { "dependencies": { "@tanstack/router-devtools-core": "^1.120.15", "solid-js": "^1.9.5" }, "peerDependencies": { "@tanstack/react-router": "^1.120.15", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-5KcUXc3fkiLo/6Y56gOM3JqmYXG1ElIH2iyUWuG5IlcegLrpXhu4OBQ+8Q4+62CD0OKy0ifUDyemrCOAEOfCvw=="],
"@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.120.16", "", { "dependencies": { "@tanstack/router-devtools-core": "^1.120.15", "solid-js": "^1.9.5" }, "peerDependencies": { "@tanstack/react-router": "^1.120.16", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-DWXmMLknVJJMGP2k5yeUWBDhJOHbV2jVfnZKxtGzA64xXhwDFgU9qpodcmYSq3+kHWsKrd7iX0wc7d27rGwGDA=="],
"@tanstack/react-start": ["@tanstack/react-start@1.120.15", "", { "dependencies": { "@tanstack/react-start-client": "^1.120.15", "@tanstack/react-start-config": "^1.120.15", "@tanstack/react-start-router-manifest": "^1.120.15", "@tanstack/react-start-server": "^1.120.15", "@tanstack/start-api-routes": "^1.120.15", "@tanstack/start-server-functions-client": "^1.120.15", "@tanstack/start-server-functions-handler": "^1.120.15", "@tanstack/start-server-functions-server": "^1.119.2", "@tanstack/start-server-functions-ssr": "^1.120.15" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "vite": "^6.0.0" } }, "sha512-xhhhfnC06JaT08k75IdoLjYLgdXCG7KNAAkPVNySImfLKEv0zJHJPbgh719ZDGSFtTNTQs0SfgH/XkzbVM3IKA=="],
"@tanstack/react-start": ["@tanstack/react-start@1.120.16", "", { "dependencies": { "@tanstack/react-start-client": "^1.120.16", "@tanstack/react-start-config": "^1.120.16", "@tanstack/react-start-router-manifest": "^1.120.15", "@tanstack/react-start-server": "^1.120.16", "@tanstack/start-api-routes": "^1.120.16", "@tanstack/start-server-functions-client": "^1.120.16", "@tanstack/start-server-functions-handler": "^1.120.16", "@tanstack/start-server-functions-server": "^1.119.2", "@tanstack/start-server-functions-ssr": "^1.120.16" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "vite": "^6.0.0" } }, "sha512-tW8pR9k/4sy2HbBdky5dNI7bq9/BPrbOtcZeaifXn72QdWH3yF5yZPswZBraFTBQB+QP9J/Fya0NwUXL9k6cyA=="],
"@tanstack/react-start-client": ["@tanstack/react-start-client@1.120.15", "", { "dependencies": { "@tanstack/react-router": "^1.120.15", "@tanstack/router-core": "^1.120.15", "@tanstack/start-client-core": "^1.120.15", "cookie-es": "^1.2.2", "jsesc": "^3.1.0", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3", "vinxi": "^0.5.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-UCIRHB2/Es8uZIce/btI218qqsmVTW5NBOlt6A6m3F++s4nrhYlP3E2FEj/RX7rhXrOB4gjj99tAz/jjR/sVXw=="],
"@tanstack/react-start-client": ["@tanstack/react-start-client@1.120.16", "", { "dependencies": { "@tanstack/react-router": "^1.120.16", "@tanstack/router-core": "^1.120.15", "@tanstack/start-client-core": "^1.120.16", "cookie-es": "^1.2.2", "jsesc": "^3.1.0", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3", "vinxi": "^0.5.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-aHtaYboWkQnmj2s9ntCsRjCdZKRuUjy06M7Cm9wXtKul4NAWpuD+L90VKjajt1UQKd0Y5uCrMS4OiMj3BsCHAg=="],
"@tanstack/react-start-config": ["@tanstack/react-start-config@1.120.15", "", { "dependencies": { "@tanstack/react-start-plugin": "^1.115.0", "@tanstack/router-core": "^1.120.15", "@tanstack/router-generator": "^1.120.15", "@tanstack/router-plugin": "^1.120.15", "@tanstack/server-functions-plugin": "^1.119.2", "@tanstack/start-server-functions-handler": "^1.120.15", "@vitejs/plugin-react": "^4.3.4", "import-meta-resolve": "^4.1.0", "nitropack": "^2.10.4", "ofetch": "^1.4.1", "vinxi": "0.5.3", "vite": "^6.1.0", "zod": "^3.24.2" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-A9/4IQkFwhR15rb5KbTfUYUpossQM8MpBMTZs5xAoucC8rAsMoBPiiWZN6g/A1YN/xkDn/ghNhAxFfnOUhWDJA=="],
"@tanstack/react-start-config": ["@tanstack/react-start-config@1.120.16", "", { "dependencies": { "@tanstack/react-start-plugin": "^1.115.0", "@tanstack/router-core": "^1.120.15", "@tanstack/router-generator": "^1.120.16", "@tanstack/router-plugin": "^1.120.16", "@tanstack/server-functions-plugin": "^1.119.2", "@tanstack/start-server-functions-handler": "^1.120.16", "@vitejs/plugin-react": "^4.3.4", "import-meta-resolve": "^4.1.0", "nitropack": "^2.10.4", "ofetch": "^1.4.1", "vinxi": "0.5.3", "vite": "^6.1.0", "zod": "^3.24.2" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-7wdn+Ny/zHOLcdwDscyAomsR5cZqo+aCmxeCUogyYzfglwyh2iMpMzcxax25A0tZWA8F55f/xAgZ5tjjwhdcWw=="],
"@tanstack/react-start-plugin": ["@tanstack/react-start-plugin@1.115.0", "", { "dependencies": { "@babel/code-frame": "7.26.2", "@babel/core": "^7.26.8", "@babel/plugin-syntax-jsx": "^7.25.9", "@babel/plugin-syntax-typescript": "^7.25.9", "@babel/template": "^7.26.8", "@babel/traverse": "^7.26.8", "@babel/types": "^7.26.8", "@tanstack/router-utils": "^1.115.0", "babel-dead-code-elimination": "^1.0.10", "tiny-invariant": "^1.3.3", "vite": "6.1.4" } }, "sha512-5ltCwlAV5XOz7x/IFoOT1992gITfkygd5BdIexN990prNpvjDiOsNwJtrSEg9rge4XCuAbSK3jtxFh4Qy0l0zA=="],
"@tanstack/react-start-router-manifest": ["@tanstack/react-start-router-manifest@1.120.15", "", { "dependencies": { "@tanstack/router-core": "^1.120.15", "tiny-invariant": "^1.3.3", "vinxi": "0.5.3" } }, "sha512-dyL0cKiihkqIy4IqzRiTAfoB7zvbPpSOM7kzyioD5ovb41ZhQ4ksN1yF/SmaLvEJSmNor4rnnNDJf4Q0VMTz+Q=="],
"@tanstack/react-start-server": ["@tanstack/react-start-server@1.120.15", "", { "dependencies": { "@tanstack/history": "^1.115.0", "@tanstack/react-router": "^1.120.15", "@tanstack/router-core": "^1.120.15", "@tanstack/start-client-core": "^1.120.15", "@tanstack/start-server-core": "^1.120.15", "h3": "1.13.0", "isbot": "^5.1.22", "jsesc": "^3.1.0", "tiny-warning": "^1.0.3", "unctx": "^2.4.1" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-oUeCbbOILfACwgkbp2jDpAXjU7aSQY8WWa2QmH3iv2IjqI+fP11rtpQ3XRG6/A9/+nNXUKPzGOHFP0yuB1fAgw=="],
"@tanstack/react-start-server": ["@tanstack/react-start-server@1.120.16", "", { "dependencies": { "@tanstack/history": "^1.115.0", "@tanstack/react-router": "^1.120.16", "@tanstack/router-core": "^1.120.15", "@tanstack/start-client-core": "^1.120.16", "@tanstack/start-server-core": "^1.120.16", "h3": "1.13.0", "isbot": "^5.1.22", "jsesc": "^3.1.0", "tiny-warning": "^1.0.3", "unctx": "^2.4.1" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-21bWbU4C8tY7li1NRU7Ft5e7fLcs8WvLZKRcpo4gbCWLcy5MhDDjHJLrg6H6UgvgemWVZ6qwy4SJ7IXRjR3FPw=="],
"@tanstack/react-store": ["@tanstack/react-store@0.7.0", "", { "dependencies": { "@tanstack/store": "0.7.0", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-S/Rq17HaGOk+tQHV/yrePMnG1xbsKZIl/VsNWnNXt4XW+tTY8dTlvpJH2ZQ3GRALsusG5K6Q3unAGJ2pd9W/Ng=="],
@ -723,29 +723,29 @@
"@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.120.15", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16" }, "peerDependencies": { "@tanstack/router-core": "^1.120.15", "csstype": "^3.0.10", "solid-js": ">=1.9.5", "tiny-invariant": "^1.3.3" }, "optionalPeers": ["csstype"] }, "sha512-AT9obPHKpJqnHMbwshozSy6sApg5LchiAll3blpS3MMDybUCidYHrdhe9MZJLmlC99IQiEGmuZERP3VRcuPNHg=="],
"@tanstack/router-generator": ["@tanstack/router-generator@1.120.15", "", { "dependencies": { "@tanstack/virtual-file-routes": "^1.115.0", "prettier": "^3.5.0", "tsx": "^4.19.2", "zod": "^3.24.2" }, "peerDependencies": { "@tanstack/react-router": "^1.120.15" }, "optionalPeers": ["@tanstack/react-router"] }, "sha512-QwZ0rNXxzgOEUDRRAEWVjofKxuxSMIYEdYC3z20k6a7jkLC6pnlCORFx41Vf4xVCO6eElqlrUKXWLTleYSsvQw=="],
"@tanstack/router-generator": ["@tanstack/router-generator@1.120.16", "", { "dependencies": { "@tanstack/virtual-file-routes": "^1.115.0", "prettier": "^3.5.0", "tsx": "^4.19.2", "zod": "^3.24.2" }, "peerDependencies": { "@tanstack/react-router": "^1.120.16" }, "optionalPeers": ["@tanstack/react-router"] }, "sha512-ekCcIPk76Nj17ZOpiRmyDhZNmE06tPSQvu19TWQi6797dDChDCozed7cQbdn8qkuo84SjBhl0r087GTUkksbDg=="],
"@tanstack/router-plugin": ["@tanstack/router-plugin@1.120.15", "", { "dependencies": { "@babel/core": "^7.26.8", "@babel/plugin-syntax-jsx": "^7.25.9", "@babel/plugin-syntax-typescript": "^7.25.9", "@babel/template": "^7.26.8", "@babel/traverse": "^7.26.8", "@babel/types": "^7.26.8", "@tanstack/router-core": "^1.120.15", "@tanstack/router-generator": "^1.120.15", "@tanstack/router-utils": "^1.115.0", "@tanstack/virtual-file-routes": "^1.115.0", "@types/babel__core": "^7.20.5", "@types/babel__template": "^7.4.4", "@types/babel__traverse": "^7.20.6", "babel-dead-code-elimination": "^1.0.10", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", "@tanstack/react-router": "^1.120.15", "vite": ">=5.0.0 || >=6.0.0", "vite-plugin-solid": "^2.11.2", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-ARuuPRKO5HzN3V0LzmkIGm0e447t5VCy2ijbUnzd08KjcJm3lG221ViC2qI+vTom1zp6yeNZHfJW1LBh1yLrTw=="],
"@tanstack/router-plugin": ["@tanstack/router-plugin@1.120.16", "", { "dependencies": { "@babel/core": "^7.26.8", "@babel/plugin-syntax-jsx": "^7.25.9", "@babel/plugin-syntax-typescript": "^7.25.9", "@babel/template": "^7.26.8", "@babel/traverse": "^7.26.8", "@babel/types": "^7.26.8", "@tanstack/router-core": "^1.120.15", "@tanstack/router-generator": "^1.120.16", "@tanstack/router-utils": "^1.115.0", "@tanstack/virtual-file-routes": "^1.115.0", "@types/babel__core": "^7.20.5", "@types/babel__template": "^7.4.4", "@types/babel__traverse": "^7.20.6", "babel-dead-code-elimination": "^1.0.10", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", "@tanstack/react-router": "^1.120.16", "vite": ">=5.0.0 || >=6.0.0", "vite-plugin-solid": "^2.11.2", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-p++CuH8FHFToueAuxwyd+vkRm5JNhJoCl47qxZLHa91iZvUwF9i0AbGYVWKXObo0EhYdVIGn4xhisPcOq872Eg=="],
"@tanstack/router-utils": ["@tanstack/router-utils@1.115.0", "", { "dependencies": { "@babel/generator": "^7.26.8", "@babel/parser": "^7.26.8", "ansis": "^3.11.0", "diff": "^7.0.0" } }, "sha512-Dng4y+uLR9b5zPGg7dHReHOTHQa6x+G6nCoZshsDtWrYsrdCcJEtLyhwZ5wG8OyYS6dVr/Cn+E5Bd2b6BhJ89w=="],
"@tanstack/server-functions-plugin": ["@tanstack/server-functions-plugin@1.119.2", "", { "dependencies": { "@babel/code-frame": "7.26.2", "@babel/core": "^7.26.8", "@babel/plugin-syntax-jsx": "^7.25.9", "@babel/plugin-syntax-typescript": "^7.25.9", "@babel/template": "^7.26.8", "@babel/traverse": "^7.26.8", "@babel/types": "^7.26.8", "@tanstack/directive-functions-plugin": "1.119.2", "babel-dead-code-elimination": "^1.0.9", "dedent": "^1.5.3", "tiny-invariant": "^1.3.3" } }, "sha512-ramMedB4yt+fhFHio3GqRLUQVwKBEBREnHEVetHEYHLe6h+qYEwaVxQrQ75J+dTTWXa14DLadtgR3ygEydtfqA=="],
"@tanstack/start-api-routes": ["@tanstack/start-api-routes@1.120.15", "", { "dependencies": { "@tanstack/router-core": "^1.120.15", "@tanstack/start-server-core": "^1.120.15", "vinxi": "0.5.3" } }, "sha512-AKxT1he6PstiyfNAP7YHotM1hpQzQfl0WwMO4L4MDi2JG2q8Mm4630xuOe5TkdWMIC38RZeSPE2V33xXiLZ+tA=="],
"@tanstack/start-api-routes": ["@tanstack/start-api-routes@1.120.16", "", { "dependencies": { "@tanstack/router-core": "^1.120.15", "@tanstack/start-server-core": "^1.120.16", "vinxi": "0.5.3" } }, "sha512-aledP0KV6ibMCq0ya7ESs/23+5pPI98e0mzZVidaYBHN/4nvhmLf4W2KFdTmE6czY8CsejTo+Q74a3L+3ZMgKw=="],
"@tanstack/start-client-core": ["@tanstack/start-client-core@1.120.15", "", { "dependencies": { "@tanstack/router-core": "^1.120.15", "cookie-es": "^1.2.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-R21uRzjqbds0aMK8ZQ12yIxyyh5pNqtv8/dg9dp44Hl6jfb3wBNfo1kAiJErBDRF50ITJJLQ7sGDIualtcdKbQ=="],
"@tanstack/start-client-core": ["@tanstack/start-client-core@1.120.16", "", { "dependencies": { "@tanstack/router-core": "^1.120.15", "cookie-es": "^1.2.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-GSPfC41tP3KP+JI6c0tlQJlJTlzQ/9k2dY91JhT3bqgoIcB9rXi+eKqW3dHBWmq4zHCRWtmom+khV4Cn61QLQQ=="],
"@tanstack/start-server-core": ["@tanstack/start-server-core@1.120.15", "", { "dependencies": { "@tanstack/history": "^1.115.0", "@tanstack/router-core": "^1.120.15", "@tanstack/start-client-core": "^1.120.15", "h3": "1.13.0", "isbot": "^5.1.22", "jsesc": "^3.1.0", "tiny-warning": "^1.0.3", "unctx": "^2.4.1" } }, "sha512-om+jDeBEXI2ckrdNNDFahDI2K+mYJhBYNGJd1QLLdO8E5+16kHuOWwi7wTZD8XB6TbZEoqz4weOJF4l0kldRgA=="],
"@tanstack/start-server-core": ["@tanstack/start-server-core@1.120.16", "", { "dependencies": { "@tanstack/history": "^1.115.0", "@tanstack/router-core": "^1.120.15", "@tanstack/start-client-core": "^1.120.16", "h3": "1.13.0", "isbot": "^5.1.22", "jsesc": "^3.1.0", "tiny-warning": "^1.0.3", "unctx": "^2.4.1" } }, "sha512-D/zXMkjbr5IPEdMxXC8KNJXGVX8g92BCECd6t28Pzw7O+Ng3S8VWz674Xhb40sFit4c4Z2n3QhwNSikWN0I43w=="],
"@tanstack/start-server-functions-client": ["@tanstack/start-server-functions-client@1.120.15", "", { "dependencies": { "@tanstack/server-functions-plugin": "^1.119.2", "@tanstack/start-server-functions-fetcher": "^1.120.15" } }, "sha512-OG7D8+fUs9kStF1JNmgLSyg9sjSE+hsfH2ly+JqDsBUFYb9pu8vjTxaQNMJxCZcpgbG9NwGqbQQ3Upa1Kb4Oog=="],
"@tanstack/start-server-functions-client": ["@tanstack/start-server-functions-client@1.120.16", "", { "dependencies": { "@tanstack/server-functions-plugin": "^1.119.2", "@tanstack/start-server-functions-fetcher": "^1.120.16" } }, "sha512-1XTP1C0fqd+WXOKKzaP7c93Z/BN2NJqaXU7Bx+Iye+KGETbBbq88J+bBtbWXci5dY3nQ7J+d5LOMaeS5HjFpgw=="],
"@tanstack/start-server-functions-fetcher": ["@tanstack/start-server-functions-fetcher@1.120.15", "", { "dependencies": { "@tanstack/router-core": "^1.120.15", "@tanstack/start-client-core": "^1.120.15" } }, "sha512-ZThhU9ya7ACcKy7QXWNE8mQYBeWWKpx7XxncDblzezkMQvAddsldG5ez3K4I1ZdPR49ySjHxlhgLHLdVX86mOg=="],
"@tanstack/start-server-functions-fetcher": ["@tanstack/start-server-functions-fetcher@1.120.16", "", { "dependencies": { "@tanstack/router-core": "^1.120.15", "@tanstack/start-client-core": "^1.120.16" } }, "sha512-YT/gZMvbgjx3KlJHfMV8ZgOr9rCksko5/jWAugxF+GLRZaERT42qnWO94s0TDACJYmFPokaMiuu0KROUssHBNg=="],
"@tanstack/start-server-functions-handler": ["@tanstack/start-server-functions-handler@1.120.15", "", { "dependencies": { "@tanstack/router-core": "^1.120.15", "@tanstack/start-client-core": "^1.120.15", "@tanstack/start-server-core": "^1.120.15", "tiny-invariant": "^1.3.3" } }, "sha512-j61H8JCaVV0TKPMazQKTxtCr9bulQ3oJNYTidOdSgY+CKOZRAfPUINXZl/CDQOTSwAYmMAiroeSR7BwUPq26pg=="],
"@tanstack/start-server-functions-handler": ["@tanstack/start-server-functions-handler@1.120.16", "", { "dependencies": { "@tanstack/router-core": "^1.120.15", "@tanstack/start-client-core": "^1.120.16", "@tanstack/start-server-core": "^1.120.16", "tiny-invariant": "^1.3.3" } }, "sha512-aAOk5nvrTubMjJoMnhgrv8DhkAm2LIsDCk/aqJ9qV4SoUXWY7fVoCKawBOv3C/npkd/F9XB7uAmADwD/LGV6Kw=="],
"@tanstack/start-server-functions-server": ["@tanstack/start-server-functions-server@1.119.2", "", { "dependencies": { "@tanstack/server-functions-plugin": "^1.119.2", "tiny-invariant": "^1.3.3" } }, "sha512-wosjaLvWhVeNCg+RRl2Q7Wl0TliQUBPlqhoG9BBN5uLNmsW+iKeFuzctaXoCB0sKkloz9gjtoJRxPyFY2LpkxQ=="],
"@tanstack/start-server-functions-ssr": ["@tanstack/start-server-functions-ssr@1.120.15", "", { "dependencies": { "@tanstack/server-functions-plugin": "^1.119.2", "@tanstack/start-client-core": "^1.120.15", "@tanstack/start-server-core": "^1.120.15", "@tanstack/start-server-functions-fetcher": "^1.120.15", "tiny-invariant": "^1.3.3" } }, "sha512-fjMX84yEcAF+K29jnXCYc5PdrZh1W4vLTooWzBjryPpHlju25/YxpCjV2mMB9yP0Ri8xgQHg5xD7ucn2yw1Dyw=="],
"@tanstack/start-server-functions-ssr": ["@tanstack/start-server-functions-ssr@1.120.16", "", { "dependencies": { "@tanstack/server-functions-plugin": "^1.119.2", "@tanstack/start-client-core": "^1.120.16", "@tanstack/start-server-core": "^1.120.16", "@tanstack/start-server-functions-fetcher": "^1.120.16", "tiny-invariant": "^1.3.3" } }, "sha512-MbbJSbW+xU8z0oyxCs6rhyJSb3PttO1+k6iQrgXYeoTEU3r1uxZQZCK3OXXmh8THh05vrIlpDIjdMl1tpPgk5g=="],
"@tanstack/store": ["@tanstack/store@0.7.0", "", {}, "sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg=="],
@ -2213,18 +2213,12 @@
"@tanstack/directive-functions-plugin/vite": ["vite@6.1.4", "", { "dependencies": { "esbuild": "^0.24.2", "postcss": "^8.5.2", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-VzONrF/qqEg/JBwHXBJdVSmBZBhwiPGinyUb0SQLByqQwi6o8UvX5TWLkpvkq3tvN8Cr273ieZDt36CGwWRMvA=="],
"@tanstack/react-start-config/zod": ["zod@3.25.49", "", {}, "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q=="],
"@tanstack/react-start-plugin/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
"@tanstack/react-start-plugin/vite": ["vite@6.1.4", "", { "dependencies": { "esbuild": "^0.24.2", "postcss": "^8.5.2", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-VzONrF/qqEg/JBwHXBJdVSmBZBhwiPGinyUb0SQLByqQwi6o8UvX5TWLkpvkq3tvN8Cr273ieZDt36CGwWRMvA=="],
"@tanstack/router-generator/zod": ["zod@3.25.49", "", {}, "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q=="],
"@tanstack/router-plugin/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"@tanstack/router-plugin/zod": ["zod@3.25.49", "", {}, "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q=="],
"@tanstack/server-functions-plugin/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
"@tanstack/server-functions-plugin/dedent": ["dedent@1.6.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA=="],

View File

@ -6,33 +6,36 @@ CREATE SCHEMA "log";
--> statement-breakpoint
CREATE SCHEMA "player";
--> statement-breakpoint
CREATE SCHEMA "web";
--> statement-breakpoint
CREATE TYPE "common"."gm_authority" AS ENUM('IMPLEMENTOR', 'HIGH_WIZARD', 'GOD', 'LOW_WIZARD', 'PLAYER');--> statement-breakpoint
CREATE TYPE "common"."spam_type" AS ENUM('GOOD', 'SPAM');--> statement-breakpoint
CREATE TYPE "log"."gold_log_how" AS ENUM('BUY', 'SELL', 'SHOP_SELL', 'SHOP_BUY', 'EXCHANGE_TAKE', 'EXCHANGE_GIVE', 'QUEST');--> statement-breakpoint
CREATE TYPE "log"."log_type" AS ENUM('ITEM', 'CHARACTER');--> statement-breakpoint
CREATE TYPE "log"."login_log_type" AS ENUM('LOGIN', 'LOGOUT');--> statement-breakpoint
CREATE TYPE "log"."money_log_type" AS ENUM('MONSTER', 'SHOP', 'REFINE', 'QUEST', 'GUILD', 'MISC', 'KILL', 'DROP');--> statement-breakpoint
CREATE TYPE "log"."refine_log_set_type" AS ENUM('SOCKET', 'POWER', 'ROD', 'GUILD', 'SCROLL', 'HYUNIRON', 'GOD_SCROLL', 'MUSIN_SCROLL');--> statement-breakpoint
CREATE TYPE "log"."reward_type" AS ENUM('EXP', 'ITEM');--> statement-breakpoint
CREATE TYPE "player"."guild_grade_auth" AS ENUM('ADD_MEMBER', 'REMOVE_MEMEBER', 'NOTICE', 'USE_SKILL');--> statement-breakpoint
CREATE TYPE "player"."item_proto_immune_flag" AS ENUM('PARA', 'CURSE', 'STUN', 'SLEEP', 'SLOW', 'POISON', 'TERROR');--> statement-breakpoint
CREATE TYPE "player"."item_attr_apply" AS ENUM('MAX_HP', 'MAX_SP', 'CON', 'INT', 'STR', 'DEX', 'ATT_SPEED', 'MOV_SPEED', 'CAST_SPEED', 'HP_REGEN', 'SP_REGEN', 'POISON_PCT', 'STUN_PCT', 'SLOW_PCT', 'CRITICAL_PCT', 'PENETRATE_PCT', 'ATTBONUS_HUMAN', 'ATTBONUS_ANIMAL', 'ATTBONUS_ORC', 'ATTBONUS_MILGYO', 'ATTBONUS_UNDEAD', 'ATTBONUS_DEVIL', 'STEAL_HP', 'STEAL_SP', 'MANA_BURN_PCT', 'DAMAGE_SP_RECOVER', 'BLOCK', 'DODGE', 'RESIST_SWORD', 'RESIST_TWOHAND', 'RESIST_DAGGER', 'RESIST_BELL', 'RESIST_FAN', 'RESIST_BOW', 'RESIST_FIRE', 'RESIST_ELEC', 'RESIST_MAGIC', 'RESIST_WIND', 'REFLECT_MELEE', 'REFLECT_CURSE', 'POISON_REDUCE', 'KILL_SP_RECOVER', 'EXP_DOUBLE_BONUS', 'GOLD_DOUBLE_BONUS', 'ITEM_DROP_BONUS', 'POTION_BONUS', 'KILL_HP_RECOVER', 'IMMUNE_STUN', 'IMMUNE_SLOW', 'IMMUNE_FALL', 'SKILL', 'BOW_DISTANCE', 'ATT_GRADE_BONUS', 'DEF_GRADE_BONUS', 'MAGIC_ATT_GRADE', 'MAGIC_DEF_GRADE', 'CURSE_PCT', 'MAX_STAMINA', 'ATTBONUS_WARRIOR', 'ATTBONUS_ASSASSIN', 'ATTBONUS_SURA', 'ATTBONUS_SHAMAN', 'ATTBONUS_MONSTER', 'MALL_ATTBONUS', 'MALL_DEFBONUS', 'MALL_EXPBONUS', 'MALL_ITEMBONUS', 'MALL_GOLDBONUS', 'MAX_HP_PCT', 'MAX_SP_PCT', 'SKILL_DAMAGE_BONUS', 'NORMAL_HIT_DAMAGE_BONUS', 'SKILL_DEFEND_BONUS', 'NORMAL_HIT_DEFEND_BONUS', 'PC_BANG_EXP_BONUS', 'PC_BANG_DROP_BONUS', 'EXTRACT_HP_PCT', 'RESIST_WARRIOR', 'RESIST_ASSASSIN', 'RESIST_SURA', 'RESIST_SHAMAN', 'ENERGY', 'DEF_GRADE', 'COSTUME_ATTR_BONUS', 'MAGIC_ATTBONUS_PER', 'MELEE_MAGIC_ATTBONUS_PER', 'RESIST_ICE', 'RESIST_EARTH', 'RESIST_DARK', 'ANTI_CRITICAL_PCT', 'ANTI_PENETRATE_PCT');--> statement-breakpoint
CREATE TYPE "player"."item_attr_rare_apply" AS ENUM('MAX_HP', 'MAX_SP', 'CON', 'INT', 'STR', 'DEX', 'ATT_SPEED', 'MOV_SPEED', 'CAST_SPEED', 'HP_REGEN', 'SP_REGEN', 'POISON_PCT', 'STUN_PCT', 'SLOW_PCT', 'CRITICAL_PCT', 'PENETRATE_PCT', 'ATTBONUS_HUMAN', 'ATTBONUS_ANIMAL', 'ATTBONUS_ORC', 'ATTBONUS_MILGYO', 'ATTBONUS_UNDEAD', 'ATTBONUS_DEVIL', 'STEAL_HP', 'STEAL_SP', 'MANA_BURN_PCT', 'DAMAGE_SP_RECOVER', 'BLOCK', 'DODGE', 'RESIST_SWORD', 'RESIST_TWOHAND', 'RESIST_DAGGER', 'RESIST_BELL', 'RESIST_FAN', 'RESIST_BOW', 'RESIST_FIRE', 'RESIST_ELEC', 'RESIST_MAGIC', 'RESIST_WIND', 'REFLECT_MELEE', 'REFLECT_CURSE', 'POISON_REDUCE', 'KILL_SP_RECOVER', 'EXP_DOUBLE_BONUS', 'GOLD_DOUBLE_BONUS', 'ITEM_DROP_BONUS', 'POTION_BONUS', 'KILL_HP_RECOVER', 'IMMUNE_STUN', 'IMMUNE_SLOW', 'IMMUNE_FALL', 'SKILL', 'BOW_DISTANCE', 'ATT_GRADE_BONUS', 'DEF_GRADE_BONUS', 'MAGIC_ATT_GRADE', 'MAGIC_DEF_GRADE', 'CURSE_PCT', 'MAX_STAMINA', 'ATT_BONUS_TO_WARRIOR', 'ATT_BONUS_TO_ASSASSIN', 'ATT_BONUS_TO_SURA', 'ATT_BONUS_TO_SHAMAN', 'ATT_BONUS_TO_MONSTER', 'NORMAL_HIT_DEFEND_BONUS', 'SKILL_DEFEND_BONUS', 'NOUSE2''NOUSE3', 'NOUSE4', 'NOUSE5', 'NOUSE6', 'NOUSE7', 'NOUSE8', 'NOUSE9', 'NOUSE10', 'NOUSE11', 'NOUSE12', 'NOUSE13', 'NOUSE14', 'RESIST_WARRIOR', 'RESIST_ASSASSIN', 'RESIST_SURA', 'RESIST_SHAMAN');--> statement-breakpoint
CREATE TYPE "player"."item_attr_rare_apply" AS ENUM('MAX_HP', 'MAX_SP', 'CON', 'INT', 'STR', 'DEX', 'ATT_SPEED', 'MOV_SPEED', 'CAST_SPEED', 'HP_REGEN', 'SP_REGEN', 'POISON_PCT', 'STUN_PCT', 'SLOW_PCT', 'CRITICAL_PCT', 'PENETRATE_PCT', 'ATTBONUS_HUMAN', 'ATTBONUS_ANIMAL', 'ATTBONUS_ORC', 'ATTBONUS_MILGYO', 'ATTBONUS_UNDEAD', 'ATTBONUS_DEVIL', 'STEAL_HP', 'STEAL_SP', 'MANA_BURN_PCT', 'DAMAGE_SP_RECOVER', 'BLOCK', 'DODGE', 'RESIST_SWORD', 'RESIST_TWOHAND', 'RESIST_DAGGER', 'RESIST_BELL', 'RESIST_FAN', 'RESIST_BOW', 'RESIST_FIRE', 'RESIST_ELEC', 'RESIST_MAGIC', 'RESIST_WIND', 'REFLECT_MELEE', 'REFLECT_CURSE', 'POISON_REDUCE', 'KILL_SP_RECOVER', 'EXP_DOUBLE_BONUS', 'GOLD_DOUBLE_BONUS', 'ITEM_DROP_BONUS', 'POTION_BONUS', 'KILL_HP_RECOVER', 'IMMUNE_STUN', 'IMMUNE_SLOW', 'IMMUNE_FALL', 'SKILL', 'BOW_DISTANCE', 'ATT_GRADE_BONUS', 'DEF_GRADE_BONUS', 'MAGIC_ATT_GRADE', 'MAGIC_DEF_GRADE', 'CURSE_PCT', 'MAX_STAMINA', 'ATT_BONUS_TO_WARRIOR', 'ATT_BONUS_TO_ASSASSIN', 'ATT_BONUS_TO_SURA', 'ATT_BONUS_TO_SHAMAN', 'ATT_BONUS_TO_MONSTER', 'NORMAL_HIT_DEFEND_BONUS', 'SKILL_DEFEND_BONUS', 'NOUSE2', 'NOUSE3', 'NOUSE4', 'NOUSE5', 'NOUSE6', 'NOUSE7', 'NOUSE8', 'NOUSE9', 'NOUSE10', 'NOUSE11', 'NOUSE12', 'NOUSE13', 'NOUSE14', 'RESIST_WARRIOR', 'RESIST_ASSASSIN', 'RESIST_SURA', 'RESIST_SHAMAN');--> statement-breakpoint
CREATE TYPE "player"."item_window" AS ENUM('INVENTORY', 'EQUIPMENT', 'SAFEBOX', 'MALL', 'DRAGON_SOUL_INVENTORY', 'BELT_INVENTORY');--> statement-breakpoint
CREATE TYPE "log"."log_type" AS ENUM('ITEM', 'CHARACTER');--> statement-breakpoint
CREATE TYPE "log"."login_log_type" AS ENUM('LOGIN', 'LOGOUT');--> statement-breakpoint
CREATE TYPE "player"."mob_ai_flag" AS ENUM('AGGR', 'NOMOVE', 'COWARD', 'NOATTSHINSU', 'NOATTCHUNJO', 'NOATTJINNO', 'ATTMOB', 'BERSERK', 'STONESKIN', 'GODSPEED', 'DEATHBLOW', 'REVIVE');--> statement-breakpoint
CREATE TYPE "player"."mob_set_immune_flag" AS ENUM('STUN', 'SLOW', 'FALL', 'CURSE', 'POISON', 'TERROR');--> statement-breakpoint
CREATE TYPE "player"."mob_set_race_flag" AS ENUM('ANIMAL', 'UNDEAD', 'DEVIL', 'HUMAN', 'ORC', 'MILGYO', 'INSECT', 'FIRE', 'ICE', 'DESERT', 'TREE', 'ATT_ELEC', 'ATT_FIRE', 'ATT_ICE', 'ATT_WIND', 'ATT_EARTH', 'ATT_DARK');--> statement-breakpoint
CREATE TYPE "player"."mob_size" AS ENUM('SMALL', 'MEDIUM', 'BIG');--> statement-breakpoint
CREATE TYPE "log"."money_log_type" AS ENUM('MONSTER', 'SHOP', 'REFINE', 'QUEST', 'GUILD', 'MISC', 'KILL', 'DROP');--> statement-breakpoint
CREATE TYPE "log"."refine_log_set_type" AS ENUM('SOCKET', 'POWER', 'ROD', 'GUILD', 'SCROLL', 'HYUNIRON', 'GOD_SCROLL', 'MUSIN_SCROLL');--> statement-breakpoint
CREATE TYPE "log"."reward_type" AS ENUM('EXP', 'ITEM');--> statement-breakpoint
CREATE TYPE "player"."skill_proto_set_affect_flag" AS ENUM('YMIR', 'INVISIBILITY', 'SPAWN', 'POISON', 'SLOW', 'STUN', 'DUNGEON_READY', 'FORCE_VISIBLE', 'BUILDING_CONSTRUCTION_SMALL', 'BUILDING_CONSTRUCTION_LARGE', 'BUILDING_UPGRADE', 'MOV_SPEED_POTION', 'ATT_SPEED_POTION', 'FISH_MIDE', 'JEONGWIHON', 'GEOMGYEONG', 'CHEONGEUN', 'GYEONGGONG', 'EUNHYUNG', 'GWIGUM', 'TERROR', 'JUMAGAP', 'HOSIN', 'BOHO', 'KWAESOK', 'MANASHIELD', 'MUYEONG', 'REVIVE_INVISIBLE', 'FIRE', 'GICHEON', 'JEUNGRYEOK');--> statement-breakpoint
CREATE TYPE "player"."skill_proto_set_affect_flag_2" AS ENUM('YMIR', 'INVISIBILITY', 'SPAWN', 'POISON', 'SLOW', 'STUN', 'DUNGEON_READY', 'FORCE_VISIBLE', 'BUILDING_CONSTRUCTION_SMALL', 'BUILDING_CONSTRUCTION_LARGE', 'BUILDING_UPGRADE', 'MOV_SPEED_POTION', 'ATT_SPEED_POTION', 'FISH_MIDE', 'JEONGWIHON', 'GEOMGYEONG', 'CHEONGEUN', 'GYEONGGONG', 'EUNHYUNG', 'GWIGUM', 'TERROR', 'JUMAGAP', 'HOSIN', 'BOHO', 'KWAESOK', 'MANASHIELD');--> statement-breakpoint
CREATE TYPE "player"."skill_proto_set_flag" AS ENUM('ATTACK', 'USE_MELEE_DAMAGE', 'COMPUTE_ATTGRADE', 'SELFONLY', 'USE_MAGIC_DAMAGE', 'USE_HP_AS_COST', 'COMPUTE_MAGIC_DAMAGE', 'SPLASH', 'GIVE_PENALTY', 'USE_ARROW_DAMAGE', 'PENETRATE', 'IGNORE_TARGET_RATING', 'ATTACK_SLOW', 'ATTACK_STUN', 'HP_ABSORB', 'SP_ABSORB', 'ATTACK_FIRE_CONT', 'REMOVE_BAD_AFFECT', 'REMOVE_GOOD_AFFECT', 'CRUSH', 'ATTACK_POISON', 'TOGGLE', 'DISABLE_BY_POINT_UP', 'CRUSH_LONG');--> statement-breakpoint
CREATE TYPE "player"."skill_proto_skill_type" AS ENUM('NORMAL', 'MELEE', 'RANGE', 'MAGIC');--> statement-breakpoint
CREATE TYPE "common"."spam_type" AS ENUM('GOOD', 'SPAM');--> statement-breakpoint
CREATE TYPE "web"."ticket_attachment_type" AS ENUM('image', 'video', 'document', 'other');--> statement-breakpoint
CREATE TABLE "account"."account" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "account"."account_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"login" varchar(30) NOT NULL,
"password" text NOT NULL,
"social_id" varchar(13) NOT NULL,
"email" varchar(64) NOT NULL,
"email" text NOT NULL,
"status" varchar(8) DEFAULT 'OK' NOT NULL,
"security_code" varchar(192),
"available_dt" timestamp DEFAULT now() NOT NULL,
@ -45,33 +48,25 @@ CREATE TABLE "account"."account" (
"fish_mind_expired_at" timestamp DEFAULT now() NOT NULL,
"marriage_fast_expired_at" timestamp DEFAULT now() NOT NULL,
"money_drop_rate_expired_at" timestamp DEFAULT now() NOT NULL,
"ip" varchar(191),
"ip" varchar(16),
"created_at" timestamp DEFAULT now() NOT NULL,
"last_played_at" timestamp,
CONSTRAINT "account_login" UNIQUE("login"),
CONSTRAINT "account_email" UNIQUE("email")
);
--> statement-breakpoint
CREATE TABLE "common"."gm_host" (
"ip" varchar(16) NOT NULL
);
--> statement-breakpoint
CREATE TABLE "common"."gm_list" (
CREATE TABLE "player"."affect" (
"player_id" integer NOT NULL,
"contact_ip" varchar(16) NOT NULL,
"server_ip" varchar(16) DEFAULT 'ALL' NOT NULL,
"authority" "common"."gm_authority" DEFAULT 'PLAYER'
"type" integer NOT NULL,
"apply_on" integer DEFAULT 0 NOT NULL,
"apply_value" integer DEFAULT 0 NOT NULL,
"flag" integer DEFAULT 0 NOT NULL,
"duration" integer DEFAULT 0 NOT NULL,
"sp_cost" integer DEFAULT 0 NOT NULL
);
--> statement-breakpoint
CREATE TABLE "common"."locale" (
"key" varchar(191) NOT NULL,
"value" varchar(191) NOT NULL
);
--> statement-breakpoint
CREATE TABLE "common"."spam_db" (
"type" "common"."spam_type" DEFAULT 'SPAM' NOT NULL,
"word" varchar(256) NOT NULL,
"score" integer DEFAULT 10 NOT NULL
CREATE TABLE "player"."banword" (
"word" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "log"."boot_log" (
@ -81,12 +76,18 @@ CREATE TABLE "log"."boot_log" (
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "player"."change_empire" (
"account_id" integer NOT NULL,
"change_count" integer,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "log"."change_name" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."change_name_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"player_id" integer NOT NULL,
"old_name" varchar(191),
"new_name" varchar(191),
"ip" varchar(20),
"ip" varchar(16),
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
@ -94,7 +95,7 @@ CREATE TABLE "log"."command_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."command_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"player_id" integer NOT NULL,
"server" integer NOT NULL,
"ip" varchar(15) NOT NULL,
"ip" varchar(16) NOT NULL,
"port" integer NOT NULL,
"username" varchar(50) NOT NULL,
"command" text NOT NULL,
@ -113,6 +114,12 @@ CREATE TABLE "log"."cube_log" (
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "web"."download" (
"id" integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name "web"."download_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"label" text NOT NULL,
"url" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "log"."dragon_slay_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."dragon_slay_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"guild_id" integer NOT NULL,
@ -133,6 +140,17 @@ CREATE TABLE "log"."fish_log" (
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "common"."gm_host" (
"ip" varchar(16) NOT NULL
);
--> statement-breakpoint
CREATE TABLE "common"."gm_list" (
"player_id" integer NOT NULL,
"contact_ip" varchar(16) NOT NULL,
"server_ip" varchar(16) DEFAULT 'ALL' NOT NULL,
"authority" "common"."gm_authority" DEFAULT 'PLAYER'
);
--> statement-breakpoint
CREATE TABLE "log"."gold_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."gold_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"player_id" integer NOT NULL,
@ -142,141 +160,6 @@ CREATE TABLE "log"."gold_log" (
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "log"."hack_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."hack_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"login" char(16) NOT NULL,
"name" char(24) NOT NULL,
"ip" char(15) NOT NULL,
"server" char(100) NOT NULL,
"why" char(191) NOT NULL,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "log"."level_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."level_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"name" char(24) NOT NULL,
"level" integer DEFAULT 0 NOT NULL,
"playtime" integer DEFAULT 0 NOT NULL,
"player_id" integer NOT NULL,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "log"."log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"type" "log"."log_type" DEFAULT 'ITEM' NOT NULL,
"who" integer DEFAULT 0 NOT NULL,
"x" integer DEFAULT 0 NOT NULL,
"y" integer DEFAULT 0 NOT NULL,
"what" integer DEFAULT 0 NOT NULL,
"how" varchar(50) NOT NULL,
"hint" varchar(70),
"ip" varchar(20),
"vnum" integer,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "log"."login_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."login_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"type" "log"."login_log_type",
"channel" integer,
"account_id" integer NOT NULL,
"player_id" integer NOT NULL,
"level" integer,
"job" integer,
"playtime" integer,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "log"."login_log_2" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."login_log_2_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"type" text,
"is_gm" boolean,
"channel" integer,
"account_id" integer NOT NULL,
"player_id" integer NOT NULL,
"ip" text,
"client_version" text,
"playtime" integer DEFAULT 0 NOT NULL,
"login_at" timestamp,
"logout_at" timestamp
);
--> statement-breakpoint
CREATE TABLE "log"."money_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."money_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"type" "log"."money_log_type",
"vnum" integer DEFAULT 0 NOT NULL,
"gold" integer DEFAULT 0 NOT NULL,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "log"."player_count" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."player_count_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"count_red" integer,
"count_yellow" integer,
"count_blue" integer,
"count_total" integer,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "log"."quest_reward_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."quest_reward_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"quest_name" varchar(32),
"player_id" integer NOT NULL,
"player_level" integer,
"reward_type" "log"."reward_type",
"reward_value1" integer,
"reward_value2" integer,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "log"."refine_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."refine_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"player_id" integer NOT NULL,
"item_name" varchar(24) NOT NULL,
"item_id" integer DEFAULT 0 NOT NULL,
"step" varchar(50) NOT NULL,
"is_success" boolean NOT NULL,
"set_type" "log"."refine_log_set_type" NOT NULL,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "log"."shout_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."shout_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"channel" integer,
"empire" integer,
"shout" varchar(350),
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "log"."speed_hack_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."speed_hack_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"player_id" integer NOT NULL,
"x" integer,
"y" integer,
"hack_count" integer,
"created_at" timestamp
);
--> statement-breakpoint
CREATE TABLE "player"."affect" (
"player_id" integer NOT NULL,
"type" integer NOT NULL,
"apply_on" integer DEFAULT 0 NOT NULL,
"apply_value" integer DEFAULT 0 NOT NULL,
"flag" integer DEFAULT 0 NOT NULL,
"duration" integer DEFAULT 0 NOT NULL,
"sp_cost" integer DEFAULT 0 NOT NULL
);
--> statement-breakpoint
CREATE TABLE "player"."banword" (
"word" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "player"."change_empire" (
"account_id" integer NOT NULL,
"change_count" integer,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "player"."guild" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "player"."guild_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"name" varchar(12) NOT NULL,
@ -306,7 +189,7 @@ CREATE TABLE "player"."guild_grade" (
"guild_id" integer NOT NULL,
"grade" integer DEFAULT 0 NOT NULL,
"name" varchar(8) NOT NULL,
"auth" "guild_grade_auth"[] DEFAULT '{}' NOT NULL,
"auth" "player"."guild_grade_auth"[] DEFAULT '{}' NOT NULL,
"auth_bits" bigint GENERATED ALWAYS AS (enum_array_to_bitmask("player"."guild_grade"."auth")) STORED
);
--> statement-breakpoint
@ -321,7 +204,7 @@ CREATE TABLE "player"."guild_member" (
);
--> statement-breakpoint
CREATE TABLE "player"."guild_war_bet" (
"login" varchar(24) NOT NULL,
"login" varchar(30) NOT NULL,
"gold" integer DEFAULT 0 NOT NULL,
"guild_id" integer NOT NULL,
"war_id" integer NOT NULL
@ -346,6 +229,16 @@ CREATE TABLE "player"."guild_war_reservation" (
"started_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "log"."hack_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."hack_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"login" varchar(30) NOT NULL,
"name" char(24) NOT NULL,
"ip" char(15) NOT NULL,
"server" char(100) NOT NULL,
"why" char(191) NOT NULL,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "player"."horse_name" (
"player_id" integer NOT NULL,
"name" varchar(24) DEFAULT 'NONAME' NOT NULL
@ -443,7 +336,7 @@ CREATE TABLE "player"."item_proto" (
"anti_flag" integer DEFAULT 0,
"flag" integer DEFAULT 0,
"wear_flag" integer DEFAULT 0,
"immune_flag" "item_proto_immune_flag"[] DEFAULT '{}' NOT NULL,
"immune_flag" "player"."item_proto_immune_flag"[] DEFAULT '{}' NOT NULL,
"gold" integer DEFAULT 0,
"shop_buy_price" integer DEFAULT 0 NOT NULL,
"refined_vnum" integer DEFAULT 0 NOT NULL,
@ -490,6 +383,60 @@ CREATE TABLE "player"."land" (
"enabled" boolean DEFAULT false NOT NULL
);
--> statement-breakpoint
CREATE TABLE "log"."level_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."level_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"name" char(24) NOT NULL,
"level" integer DEFAULT 0 NOT NULL,
"playtime" integer DEFAULT 0 NOT NULL,
"player_id" integer NOT NULL,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "common"."locale" (
"key" varchar(191) NOT NULL,
"value" varchar(191) NOT NULL
);
--> statement-breakpoint
CREATE TABLE "log"."log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"type" "log"."log_type" DEFAULT 'ITEM' NOT NULL,
"who" integer DEFAULT 0 NOT NULL,
"x" integer DEFAULT 0 NOT NULL,
"y" integer DEFAULT 0 NOT NULL,
"what" integer DEFAULT 0 NOT NULL,
"how" varchar(50) NOT NULL,
"hint" varchar(70),
"ip" varchar(16),
"vnum" integer,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "log"."login_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."login_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"type" "log"."login_log_type",
"channel" integer,
"account_id" integer NOT NULL,
"player_id" integer NOT NULL,
"level" integer,
"job" integer,
"playtime" integer,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "log"."login_log_2" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."login_log_2_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"type" text,
"is_gm" boolean,
"channel" integer,
"account_id" integer NOT NULL,
"player_id" integer NOT NULL,
"ip" text,
"client_version" text,
"playtime" integer DEFAULT 0 NOT NULL,
"login_at" timestamp,
"logout_at" timestamp
);
--> statement-breakpoint
CREATE TABLE "player"."lotto_list" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "player"."lotto_list_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"server" varchar(20),
@ -519,7 +466,7 @@ CREATE TABLE "player"."mob_proto" (
"battle_type" integer DEFAULT 0 NOT NULL,
"level" integer DEFAULT 1 NOT NULL,
"size" "player"."mob_size" DEFAULT 'SMALL',
"ai_flag" "mob_ai_flag"[] DEFAULT '{}' NOT NULL,
"ai_flag" "player"."mob_ai_flag"[] DEFAULT '{}' NOT NULL,
"mount_capacity" integer DEFAULT 0 NOT NULL,
"set_race_flag" "player"."mob_set_race_flag" NOT NULL,
"set_immune_flag" "player"."mob_set_immune_flag",
@ -605,6 +552,14 @@ CREATE TABLE "player"."monarch_election" (
"election_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "log"."money_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."money_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"type" "log"."money_log_type",
"vnum" integer DEFAULT 0 NOT NULL,
"gold" integer DEFAULT 0 NOT NULL,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "player"."myshop_pricelist" (
"owner_id" integer NOT NULL,
"item_vnum" integer DEFAULT 0 NOT NULL,
@ -642,6 +597,14 @@ CREATE TABLE "player"."object_proto" (
"dependent_group" integer DEFAULT 0 NOT NULL
);
--> statement-breakpoint
CREATE TABLE "web"."password_reset_session" (
"id" text PRIMARY KEY NOT NULL,
"user_id" integer NOT NULL,
"email" text NOT NULL,
"code" text NOT NULL,
"expires_at" timestamp with time zone NOT NULL
);
--> statement-breakpoint
CREATE TABLE "player"."player" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "player"."player_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"account_id" integer NOT NULL,
@ -673,7 +636,7 @@ CREATE TABLE "player"."player" (
"stat_point" integer NOT NULL,
"skill_point" integer NOT NULL,
"quick_slot" "bytea",
"ip" varchar(15) DEFAULT '0.0.0.0',
"ip" varchar(16) DEFAULT '0.0.0.0',
"part_main" integer DEFAULT 0 NOT NULL,
"part_base" integer DEFAULT 0 NOT NULL,
"part_hair" integer DEFAULT 0 NOT NULL,
@ -693,6 +656,15 @@ CREATE TABLE "player"."player" (
"bank_value" integer DEFAULT 0 NOT NULL
);
--> statement-breakpoint
CREATE TABLE "log"."player_count" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."player_count_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"count_red" integer,
"count_yellow" integer,
"count_blue" integer,
"count_total" integer,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "player"."player_deleted" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "player"."player_deleted_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"account_id" integer NOT NULL,
@ -724,7 +696,7 @@ CREATE TABLE "player"."player_deleted" (
"stat_point" integer NOT NULL,
"skill_point" integer NOT NULL,
"quick_slot" integer,
"ip" varchar(15) DEFAULT '0.0.0.0',
"ip" varchar(16) DEFAULT '0.0.0.0',
"part_main" integer NOT NULL,
"part_base" integer DEFAULT 0 NOT NULL,
"part_hair" integer NOT NULL,
@ -759,6 +731,28 @@ CREATE TABLE "player"."quest" (
"value" integer DEFAULT 0 NOT NULL
);
--> statement-breakpoint
CREATE TABLE "log"."quest_reward_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."quest_reward_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"quest_name" varchar(32),
"player_id" integer NOT NULL,
"player_level" integer,
"reward_type" "log"."reward_type",
"reward_value1" integer,
"reward_value2" integer,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "log"."refine_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."refine_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"player_id" integer NOT NULL,
"item_name" varchar(24) NOT NULL,
"item_id" integer DEFAULT 0 NOT NULL,
"step" varchar(50) NOT NULL,
"is_success" boolean NOT NULL,
"set_type" "log"."refine_log_set_type" NOT NULL,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "player"."refine_proto" (
"id" integer PRIMARY KEY NOT NULL,
"vnum_0" integer DEFAULT 0 NOT NULL,
@ -784,6 +778,13 @@ CREATE TABLE "player"."safebox" (
"gold" integer DEFAULT 0 NOT NULL
);
--> statement-breakpoint
CREATE TABLE "web"."session" (
"id" text PRIMARY KEY NOT NULL,
"user_id" integer NOT NULL,
"ip" varchar(16) DEFAULT '0.0.0.0' NOT NULL,
"expires_at" timestamp with time zone NOT NULL
);
--> statement-breakpoint
CREATE TABLE "player"."shop" (
"vnum" integer PRIMARY KEY NOT NULL,
"name" varchar(32) DEFAULT 'NONAME' NOT NULL,
@ -797,6 +798,14 @@ CREATE TABLE "player"."shop_item" (
CONSTRAINT "shop_vnum_unique" UNIQUE("shop_vnum","item_vnum","count")
);
--> statement-breakpoint
CREATE TABLE "log"."shout_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."shout_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"channel" integer,
"empire" integer,
"shout" varchar(350),
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "player"."skill_proto" (
"vnum" integer DEFAULT 0 NOT NULL,
"name" text NOT NULL,
@ -812,7 +821,7 @@ CREATE TABLE "player"."skill_proto" (
"cooldown_poly" varchar(100) NOT NULL,
"master_bonus_poly" varchar(100) NOT NULL,
"attack_grade_poly" varchar(100) NOT NULL,
"set_flag" "skill_proto_set_flag"[] DEFAULT '{}' NOT NULL,
"set_flag" "player"."skill_proto_set_flag"[] DEFAULT '{}' NOT NULL,
"set_flag_bits" bigint GENERATED ALWAYS AS (enum_array_to_bitmask("player"."skill_proto"."set_flag")) STORED,
"set_affect_flag" "player"."skill_proto_set_affect_flag",
"set_affect_flag_bits" bigint GENERATED ALWAYS AS (enum_to_bitmask("player"."skill_proto"."set_affect_flag")) STORED,
@ -835,30 +844,82 @@ CREATE TABLE "player"."skill_proto" (
"splash_range" integer DEFAULT 0 NOT NULL
);
--> statement-breakpoint
ALTER TABLE "common"."gm_list" ADD CONSTRAINT "gm_list_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE TABLE "common"."spam_db" (
"type" "common"."spam_type" DEFAULT 'SPAM' NOT NULL,
"word" varchar(256) NOT NULL,
"score" integer DEFAULT 10 NOT NULL
);
--> statement-breakpoint
CREATE TABLE "log"."speed_hack_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "log"."speed_hack_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"player_id" integer NOT NULL,
"x" integer,
"y" integer,
"hack_count" integer,
"created_at" timestamp
);
--> statement-breakpoint
CREATE TABLE "web"."ticket" (
"id" integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name "web"."ticket_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"userId" integer NOT NULL,
"categoryId" integer NOT NULL,
"title" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp,
"closed_at" timestamp
);
--> statement-breakpoint
CREATE TABLE "web"."ticket_attachment" (
"id" integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name "web"."ticket_attachment_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"ticket_comment_id" integer NOT NULL,
"preview" jsonb,
"type" "web"."ticket_attachment_type" DEFAULT 'other' NOT NULL,
"size" integer NOT NULL,
"name" text NOT NULL,
"url" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "web"."ticket_category" (
"id" integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name "web"."ticket_category_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"label" text NOT NULL,
"description" text,
"published_at" timestamp
);
--> statement-breakpoint
CREATE TABLE "web"."ticket_comment" (
"id" integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name "web"."ticket_comment_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"userId" integer NOT NULL,
"ticketId" integer NOT NULL,
"comment" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp
);
--> statement-breakpoint
ALTER TABLE "player"."affect" ADD CONSTRAINT "affect_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."change_empire" ADD CONSTRAINT "change_empire_account_id_account_id_fk" FOREIGN KEY ("account_id") REFERENCES "account"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "log"."change_name" ADD CONSTRAINT "change_name_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "log"."command_log" ADD CONSTRAINT "command_log_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "log"."fish_log" ADD CONSTRAINT "fish_log_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "common"."gm_list" ADD CONSTRAINT "gm_list_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "log"."gold_log" ADD CONSTRAINT "gold_log_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_comment" ADD CONSTRAINT "guild_comment_guild_id_guild_id_fk" FOREIGN KEY ("guild_id") REFERENCES "player"."guild"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_grade" ADD CONSTRAINT "guild_grade_guild_id_guild_id_fk" FOREIGN KEY ("guild_id") REFERENCES "player"."guild"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_member" ADD CONSTRAINT "guild_member_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_member" ADD CONSTRAINT "guild_member_guild_id_guild_id_fk" FOREIGN KEY ("guild_id") REFERENCES "player"."guild"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_war_bet" ADD CONSTRAINT "guild_war_bet_login_account_login_fk" FOREIGN KEY ("login") REFERENCES "account"."account"("login") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_war_bet" ADD CONSTRAINT "guild_war_bet_guild_id_guild_id_fk" FOREIGN KEY ("guild_id") REFERENCES "player"."guild"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_war_bet" ADD CONSTRAINT "guild_war_bet_war_id_guild_war_reservation_id_fk" FOREIGN KEY ("war_id") REFERENCES "player"."guild_war_reservation"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_war_reservation" ADD CONSTRAINT "guild_war_reservation_guild1_guild_id_fk" FOREIGN KEY ("guild1") REFERENCES "player"."guild"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_war_reservation" ADD CONSTRAINT "guild_war_reservation_guild2_guild_id_fk" FOREIGN KEY ("guild2") REFERENCES "player"."guild"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "log"."hack_log" ADD CONSTRAINT "hack_log_login_account_login_fk" FOREIGN KEY ("login") REFERENCES "account"."account"("login") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."horse_name" ADD CONSTRAINT "horse_name_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."item_award" ADD CONSTRAINT "item_award_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."item_award" ADD CONSTRAINT "item_award_login_account_login_fk" FOREIGN KEY ("login") REFERENCES "account"."account"("login") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "log"."level_log" ADD CONSTRAINT "level_log_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "log"."login_log" ADD CONSTRAINT "login_log_account_id_account_id_fk" FOREIGN KEY ("account_id") REFERENCES "account"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "log"."login_log" ADD CONSTRAINT "login_log_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "log"."login_log_2" ADD CONSTRAINT "login_log_2_account_id_account_id_fk" FOREIGN KEY ("account_id") REFERENCES "account"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "log"."login_log_2" ADD CONSTRAINT "login_log_2_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "log"."refine_log" ADD CONSTRAINT "refine_log_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "log"."speed_hack_log" ADD CONSTRAINT "speed_hack_log_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."affect" ADD CONSTRAINT "affect_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."change_empire" ADD CONSTRAINT "change_empire_account_id_account_id_fk" FOREIGN KEY ("account_id") REFERENCES "account"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_comment" ADD CONSTRAINT "guild_comment_guild_id_guild_id_fk" FOREIGN KEY ("guild_id") REFERENCES "player"."guild"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_grade" ADD CONSTRAINT "guild_grade_guild_id_guild_id_fk" FOREIGN KEY ("guild_id") REFERENCES "player"."guild"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_member" ADD CONSTRAINT "guild_member_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_member" ADD CONSTRAINT "guild_member_guild_id_guild_id_fk" FOREIGN KEY ("guild_id") REFERENCES "player"."guild"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_war_bet" ADD CONSTRAINT "guild_war_bet_guild_id_guild_id_fk" FOREIGN KEY ("guild_id") REFERENCES "player"."guild"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_war_bet" ADD CONSTRAINT "guild_war_bet_war_id_guild_war_reservation_id_fk" FOREIGN KEY ("war_id") REFERENCES "player"."guild_war_reservation"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_war_reservation" ADD CONSTRAINT "guild_war_reservation_guild1_guild_id_fk" FOREIGN KEY ("guild1") REFERENCES "player"."guild"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."guild_war_reservation" ADD CONSTRAINT "guild_war_reservation_guild2_guild_id_fk" FOREIGN KEY ("guild2") REFERENCES "player"."guild"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."horse_name" ADD CONSTRAINT "horse_name_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."item_award" ADD CONSTRAINT "item_award_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."lotto_list" ADD CONSTRAINT "lotto_list_playerId_player_id_fk" FOREIGN KEY ("playerId") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."marriage" ADD CONSTRAINT "marriage_player_id_1_player_id_fk" FOREIGN KEY ("player_id_1") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."marriage" ADD CONSTRAINT "marriage_player_id_2_player_id_fk" FOREIGN KEY ("player_id_2") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
@ -866,14 +927,22 @@ ALTER TABLE "player"."monarch" ADD CONSTRAINT "monarch_player_id_player_id_fk" F
ALTER TABLE "player"."monarch_candidacy" ADD CONSTRAINT "monarch_candidacy_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."monarch_election" ADD CONSTRAINT "monarch_election_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."monarch_election" ADD CONSTRAINT "monarch_election_selected_player_id_player_id_fk" FOREIGN KEY ("selected_player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "web"."password_reset_session" ADD CONSTRAINT "password_reset_session_user_id_account_id_fk" FOREIGN KEY ("user_id") REFERENCES "account"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."player" ADD CONSTRAINT "player_account_id_account_id_fk" FOREIGN KEY ("account_id") REFERENCES "account"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."player_deleted" ADD CONSTRAINT "player_deleted_account_id_account_id_fk" FOREIGN KEY ("account_id") REFERENCES "account"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."player_index" ADD CONSTRAINT "player_index_account_id_account_id_fk" FOREIGN KEY ("account_id") REFERENCES "account"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."quest" ADD CONSTRAINT "quest_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "log"."refine_log" ADD CONSTRAINT "refine_log_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."safebox" ADD CONSTRAINT "safebox_account_id_account_id_fk" FOREIGN KEY ("account_id") REFERENCES "account"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "web"."session" ADD CONSTRAINT "session_user_id_account_id_fk" FOREIGN KEY ("user_id") REFERENCES "account"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."shop_item" ADD CONSTRAINT "shop_item_shop_vnum_shop_vnum_fk" FOREIGN KEY ("shop_vnum") REFERENCES "player"."shop"("vnum") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "log"."speed_hack_log" ADD CONSTRAINT "speed_hack_log_player_id_player_id_fk" FOREIGN KEY ("player_id") REFERENCES "player"."player"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "web"."ticket" ADD CONSTRAINT "ticket_userId_account_id_fk" FOREIGN KEY ("userId") REFERENCES "account"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "web"."ticket" ADD CONSTRAINT "ticket_categoryId_ticket_category_id_fk" FOREIGN KEY ("categoryId") REFERENCES "web"."ticket_category"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "web"."ticket_attachment" ADD CONSTRAINT "ticket_attachment_ticket_comment_id_ticket_comment_id_fk" FOREIGN KEY ("ticket_comment_id") REFERENCES "web"."ticket_comment"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "web"."ticket_comment" ADD CONSTRAINT "ticket_comment_userId_account_id_fk" FOREIGN KEY ("userId") REFERENCES "account"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "web"."ticket_comment" ADD CONSTRAINT "ticket_comment_ticketId_ticket_id_fk" FOREIGN KEY ("ticketId") REFERENCES "web"."ticket"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "account_social_id" ON "account"."account" USING btree ("social_id");--> statement-breakpoint
CREATE INDEX "locale_key" ON "common"."locale" USING btree ("key");--> statement-breakpoint
CREATE INDEX "cube_log_player_id" ON "log"."cube_log" USING btree ("player_id");--> statement-breakpoint
CREATE INDEX "cube_log_item_vnum" ON "log"."cube_log" USING btree ("item_vnum");--> statement-breakpoint
CREATE INDEX "cube_log_item_uid" ON "log"."cube_log" USING btree ("item_uid");--> statement-breakpoint
@ -881,13 +950,6 @@ CREATE INDEX "gold_log_created_at" ON "log"."gold_log" USING btree ("created_at"
CREATE INDEX "gold_log_player_id" ON "log"."gold_log" USING btree ("player_id");--> statement-breakpoint
CREATE INDEX "gold_log_what" ON "log"."gold_log" USING btree ("what");--> statement-breakpoint
CREATE INDEX "gold_log_how" ON "log"."gold_log" USING btree ("how");--> statement-breakpoint
CREATE INDEX "log_who" ON "log"."log" USING btree ("who");--> statement-breakpoint
CREATE INDEX "log_what" ON "log"."log" USING btree ("what");--> statement-breakpoint
CREATE INDEX "log_how" ON "log"."log" USING btree ("how");--> statement-breakpoint
CREATE INDEX "login_log_player_id" ON "log"."login_log" USING btree ("player_id","type");--> statement-breakpoint
CREATE INDEX "money_log_type" ON "log"."money_log" USING btree ("type","vnum");--> statement-breakpoint
CREATE INDEX "quest_reward_log_player_id" ON "log"."quest_reward_log" USING btree ("player_id");--> statement-breakpoint
CREATE INDEX "shout_log_created_at" ON "log"."shout_log" USING btree ("created_at");--> statement-breakpoint
CREATE INDEX "aaa" ON "player"."guild_comment" USING btree ("notice","id","guild_id");--> statement-breakpoint
CREATE INDEX "guild_comment_guild_id" ON "player"."guild_comment" USING btree ("guild_id");--> statement-breakpoint
CREATE INDEX "item_owner_id" ON "player"."item" USING btree ("owner_id");--> statement-breakpoint
@ -896,6 +958,12 @@ CREATE INDEX "item_vnum" ON "player"."item" USING btree ("vnum");--> statement-b
CREATE INDEX "item_award_player_id" ON "player"."item_award" USING btree ("player_id");--> statement-breakpoint
CREATE INDEX "item_award_given_at" ON "player"."item_award" USING btree ("give_at");--> statement-breakpoint
CREATE INDEX "item_award_taken_at" ON "player"."item_award" USING btree ("taken_at");--> statement-breakpoint
CREATE INDEX "locale_key" ON "common"."locale" USING btree ("key");--> statement-breakpoint
CREATE INDEX "log_who" ON "log"."log" USING btree ("who");--> statement-breakpoint
CREATE INDEX "log_what" ON "log"."log" USING btree ("what");--> statement-breakpoint
CREATE INDEX "log_how" ON "log"."log" USING btree ("how");--> statement-breakpoint
CREATE INDEX "login_log_player_id" ON "log"."login_log" USING btree ("player_id","type");--> statement-breakpoint
CREATE INDEX "money_log_type" ON "log"."money_log" USING btree ("type","vnum");--> statement-breakpoint
CREATE INDEX "object_proto_vnum" ON "player"."object_proto" USING btree ("vnum");--> statement-breakpoint
CREATE INDEX "object_proto_group_vnum" ON "player"."object_proto" USING btree ("group_vnum");--> statement-breakpoint
CREATE INDEX "object_proto_upgrade_vnum" ON "player"."object_proto" USING btree ("upgrade_vnum");--> statement-breakpoint
@ -907,7 +975,9 @@ CREATE INDEX "player_index_empire" ON "player"."player_index" USING btree ("empi
CREATE INDEX "quest_player_id" ON "player"."quest" USING btree ("player_id");--> statement-breakpoint
CREATE INDEX "quest_name" ON "player"."quest" USING btree ("name");--> statement-breakpoint
CREATE INDEX "quest_state" ON "player"."quest" USING btree ("state");--> statement-breakpoint
CREATE INDEX "quest_reward_log_player_id" ON "log"."quest_reward_log" USING btree ("player_id");--> statement-breakpoint
CREATE INDEX "refine_proto_src_vnum" ON "player"."refine_proto" USING btree ("src_vnum");--> statement-breakpoint
CREATE INDEX "refine_proto_result_vnum" ON "player"."refine_proto" USING btree ("result_vnum");--> statement-breakpoint
CREATE INDEX "shop_vnum" ON "player"."shop" USING btree ("vnum");--> statement-breakpoint
CREATE INDEX "shop_item_item_vnum" ON "player"."shop_item" USING btree ("item_vnum");
CREATE INDEX "shop_item_item_vnum" ON "player"."shop_item" USING btree ("item_vnum");--> statement-breakpoint
CREATE INDEX "shout_log_created_at" ON "log"."shout_log" USING btree ("created_at");

View File

@ -1,30 +0,0 @@
CREATE SCHEMA "web";
--> statement-breakpoint
CREATE TABLE "web"."password_reset_session" (
"id" text PRIMARY KEY NOT NULL,
"user_id" integer NOT NULL,
"email" text NOT NULL,
"code" text NOT NULL,
"expires_at" timestamp NOT NULL
);
--> statement-breakpoint
CREATE TABLE "web"."session" (
"id" text PRIMARY KEY NOT NULL,
"user_id" integer NOT NULL,
"ip" varchar(16) DEFAULT '0.0.0.0' NOT NULL,
"expires_at" timestamp NOT NULL
);
--> statement-breakpoint
ALTER TABLE "account"."account" ALTER COLUMN "email" SET DATA TYPE text;--> statement-breakpoint
ALTER TABLE "account"."account" ALTER COLUMN "ip" SET DATA TYPE varchar(16);--> statement-breakpoint
ALTER TABLE "log"."change_name" ALTER COLUMN "ip" SET DATA TYPE varchar(16);--> statement-breakpoint
ALTER TABLE "log"."command_log" ALTER COLUMN "ip" SET DATA TYPE varchar(16);--> statement-breakpoint
ALTER TABLE "log"."log" ALTER COLUMN "ip" SET DATA TYPE varchar(16);--> statement-breakpoint
ALTER TABLE "player"."player" ALTER COLUMN "ip" SET DATA TYPE varchar(16);--> statement-breakpoint
ALTER TABLE "player"."player_deleted" ALTER COLUMN "ip" SET DATA TYPE varchar(16);--> statement-breakpoint
ALTER TABLE "web"."password_reset_session" ADD CONSTRAINT "password_reset_session_user_id_account_id_fk" FOREIGN KEY ("user_id") REFERENCES "account"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "web"."session" ADD CONSTRAINT "session_user_id_account_id_fk" FOREIGN KEY ("user_id") REFERENCES "account"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "player"."item_attr_rare" ALTER COLUMN "apply" SET DATA TYPE text;--> statement-breakpoint
DROP TYPE "player"."item_attr_rare_apply";--> statement-breakpoint
CREATE TYPE "player"."item_attr_rare_apply" AS ENUM('MAX_HP', 'MAX_SP', 'CON', 'INT', 'STR', 'DEX', 'ATT_SPEED', 'MOV_SPEED', 'CAST_SPEED', 'HP_REGEN', 'SP_REGEN', 'POISON_PCT', 'STUN_PCT', 'SLOW_PCT', 'CRITICAL_PCT', 'PENETRATE_PCT', 'ATTBONUS_HUMAN', 'ATTBONUS_ANIMAL', 'ATTBONUS_ORC', 'ATTBONUS_MILGYO', 'ATTBONUS_UNDEAD', 'ATTBONUS_DEVIL', 'STEAL_HP', 'STEAL_SP', 'MANA_BURN_PCT', 'DAMAGE_SP_RECOVER', 'BLOCK', 'DODGE', 'RESIST_SWORD', 'RESIST_TWOHAND', 'RESIST_DAGGER', 'RESIST_BELL', 'RESIST_FAN', 'RESIST_BOW', 'RESIST_FIRE', 'RESIST_ELEC', 'RESIST_MAGIC', 'RESIST_WIND', 'REFLECT_MELEE', 'REFLECT_CURSE', 'POISON_REDUCE', 'KILL_SP_RECOVER', 'EXP_DOUBLE_BONUS', 'GOLD_DOUBLE_BONUS', 'ITEM_DROP_BONUS', 'POTION_BONUS', 'KILL_HP_RECOVER', 'IMMUNE_STUN', 'IMMUNE_SLOW', 'IMMUNE_FALL', 'SKILL', 'BOW_DISTANCE', 'ATT_GRADE_BONUS', 'DEF_GRADE_BONUS', 'MAGIC_ATT_GRADE', 'MAGIC_DEF_GRADE', 'CURSE_PCT', 'MAX_STAMINA', 'ATT_BONUS_TO_WARRIOR', 'ATT_BONUS_TO_ASSASSIN', 'ATT_BONUS_TO_SURA', 'ATT_BONUS_TO_SHAMAN', 'ATT_BONUS_TO_MONSTER', 'NORMAL_HIT_DEFEND_BONUS', 'SKILL_DEFEND_BONUS', 'NOUSE2', 'NOUSE3', 'NOUSE4', 'NOUSE5', 'NOUSE6', 'NOUSE7', 'NOUSE8', 'NOUSE9', 'NOUSE10', 'NOUSE11', 'NOUSE12', 'NOUSE13', 'NOUSE14', 'RESIST_WARRIOR', 'RESIST_ASSASSIN', 'RESIST_SURA', 'RESIST_SHAMAN');--> statement-breakpoint
ALTER TABLE "player"."item_attr_rare" ALTER COLUMN "apply" SET DATA TYPE "player"."item_attr_rare_apply" USING "apply"::"player"."item_attr_rare_apply";

View File

@ -1,2 +0,0 @@
ALTER TABLE "web"."password_reset_session" ALTER COLUMN "expires_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
ALTER TABLE "web"."session" ALTER COLUMN "expires_at" SET DATA TYPE timestamp with time zone;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -12,30 +12,16 @@
{
"idx": 1,
"version": "7",
"when": 1748556898510,
"when": 1749163873813,
"tag": "0001_init",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1748557058507,
"when": 1749163896680,
"tag": "0002_seed",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1748942645302,
"tag": "0003_worried_hex",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1748943047244,
"tag": "0004_mysterious_wallop",
"breakpoints": true
}
]
}

125
messages/de.json Normal file
View File

@ -0,0 +1,125 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"example_message": "Hallo {username}",
"navigation": {
"home": "Heim",
"rankings": "Ranglisten",
"player-rankings": "Spieler-Ranglisten",
"guild-rankings": "Gildenranglisten",
"download": "Herunterladen",
"support": "Unterstützung",
"sign-in": "anmelden",
"toggle-language": "Sprache ändern",
"my-account": "Mein Konto",
"profile": "Profil",
"sign-out": "Abmelden"
},
"languages": {
"title": "Sprachen",
"en": "Englisch",
"pt": "Portugiesisch",
"de": "Deutsch",
"ro": "rumänisch"
},
"errors": {
"unauthorized": "Sie haben keine Berechtigung, diese Ressource anzuzeigen",
"unprocessable-entity": "Wir konnten Ihre Anfrage nicht bestätigen",
"server-error": "Ups... Wir hatten ein Problem auf unserer Seite"
},
"auth": {
"validation": {
"invalid-credentials": "Ungültige Anmeldeinformationen"
}
},
"validation": {
"invalid-credentials": "Ungültige Anmeldeinformationen"
},
"change-email": {
"mutation": {
"pending": "E-Mail ändern...",
"success": "E-Mail erfolgreich geändert!"
},
"form": {
"password": "Aktuelles Passwort",
"email": "Neue E-Mail",
"submit": "E-Mail ändern"
}
},
"change-password": {
"mutation": {
"pending": "Passwort ändern...",
"success": "Passwort erfolgreich geändert!"
},
"form": {
"password": "Aktuelles Passwort",
"newPassword": "Neues Passwort",
"newPasswordConfirm": "Neues Passwort bestätigen",
"submit": "Kennwort ändern"
}
},
"sign-in": {
"modal": {
"title": "anmelden",
"description": "Schön, Sie wiederzusehen! Melden Sie sich an."
},
"mutation": {
"pending": "Anmeldung...",
"success": "Erfolgreich angemeldet, jetzt umleiten"
},
"form": {
"password": "Passwort",
"newPassword": "Neues Passwort",
"newPasswordConfirm": "Neues Passwort bestätigen",
"submit": "anmelden",
"login": "Benutzername"
},
"links": {
"forgot-password": "Passwort vergessen?",
"register": "Sie haben noch kein Konto?",
"sign-up": "Sie haben noch kein Konto?"
}
},
"sign-up": {
"modal": {
"title": "anmelden",
"description": "Schön, Sie wiederzusehen! Melden Sie sich an."
},
"mutation": {
"pending": "Anmeldung...",
"success": "Erfolgreich angemeldet, jetzt umleiten"
},
"form": {
"password": "Passwort",
"newPassword": "Neues Passwort",
"newPasswordConfirm": "Neues Passwort bestätigen",
"submit": "Melden Sie sich an",
"login": "Benutzername",
"email": "E-Mail",
"socialId": "Zeichenlöschcode"
},
"links": {
"sign-in": "Hast du schon ein Konto?"
}
},
"forgot-password": {
"mutation": {
"pending": "E-Mail zum Zurücksetzen des Passworts wird gesendet …",
"success": "E-Mail zum Zurücksetzen des Passworts erfolgreich gesendet"
},
"form": {
"email": "E-Mail",
"submit": "Wiederhersstellung des Passwortes"
}
},
"reset-password": {
"mutation": {
"pending": "Passwort ändern...",
"success": "Passwort erfolgreich geändert"
},
"form": {
"email": "E-Mail",
"password": "Neues Passwort",
"submit": "Passwort zurücksetzen"
}
}
}

125
messages/en.json Normal file
View File

@ -0,0 +1,125 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"example_message": "Hello {username}",
"navigation": {
"home": "Home",
"rankings": "Rankings",
"player-rankings": "Player Rankings",
"guild-rankings": "Guild Rankings",
"download": "Download",
"support": "Support",
"sign-in": "Sign in",
"toggle-language": "Change language",
"my-account": "My account",
"profile": "Profile",
"sign-out": "Sign out"
},
"languages": {
"title": "Languages",
"en": "English",
"pt": "Português",
"de": "German",
"ro": "Romanian"
},
"errors": {
"unauthorized": "You do not have authorization to view this resource",
"unprocessable-entity": "We could not validate your request",
"server-error": "Ooops... We had an issue on our side"
},
"auth": {
"validation": {
"invalid-credentials": "Invalid credentials"
}
},
"validation": {
"invalid-credentials": "Invalid credentials"
},
"change-email": {
"mutation": {
"pending": "Changing email...",
"success": "Successfully changed email!"
},
"form": {
"password": "Current password",
"email": "New email",
"submit": "Change email"
}
},
"change-password": {
"mutation": {
"pending": "Changing password...",
"success": "Successfully changed password!"
},
"form": {
"password": "Current password",
"newPassword": "New password",
"newPasswordConfirm": "Confirm new password",
"submit": "Change password"
}
},
"sign-in": {
"modal": {
"title": "Sign in",
"description": "Good to see you again! Let's get you signed in."
},
"mutation": {
"pending": "Signing in...",
"success": "Signed in successfully, redirecting now"
},
"form": {
"password": "Password",
"newPassword": "New password",
"newPasswordConfirm": "Confirm new password",
"submit": "Sign in",
"login": "Username"
},
"links": {
"forgot-password": "Forgot your password?",
"register": "Don't have an account yet?",
"sign-up": "Don't have an account yet?"
}
},
"sign-up": {
"modal": {
"title": "Sign in",
"description": "Good to see you again! Let's get you signed in."
},
"mutation": {
"pending": "Signing up...",
"success": "Signed up successfully, redirecting now"
},
"form": {
"password": "Password",
"newPassword": "New password",
"newPasswordConfirm": "Confirm new password",
"submit": "Sign up",
"login": "Username",
"email": "Email",
"socialId": "Character deletion code"
},
"links": {
"sign-in": "Already have an account?"
}
},
"forgot-password": {
"mutation": {
"pending": "Sending password reset email...",
"success": "Password reset email sent successfully"
},
"form": {
"email": "Email",
"submit": "Recover password"
}
},
"reset-password": {
"mutation": {
"pending": "Changing password...",
"success": "Password changed successfully"
},
"form": {
"email": "Email",
"password": "New password",
"submit": "Reset password"
}
}
}

125
messages/pt.json Normal file
View File

@ -0,0 +1,125 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"example_message": "Olá {username}",
"navigation": {
"home": "Inicio",
"rankings": "Rankings",
"player-rankings": "Ranking de Jogadores",
"guild-rankings": "Ranking de Guilds",
"download": "Download",
"support": "Suporte",
"sign-in": "Login",
"toggle-language": "Alterar idioma",
"my-account": "Minha conta",
"profile": "Perfil",
"sign-out": "Sair"
},
"languages": {
"en": "English",
"pt": "Português",
"title": "Idiomas",
"de": "Alemão",
"ro": "romeno"
},
"errors": {
"unauthorized": "Você não tem autorização para visualizar este recurso",
"unprocessable-entity": "Não foi possível validar sua a solicitação",
"server-error": "Ops... Tivemos um problema do nosso lado"
},
"auth": {
"validation": {
"invalid-credentials": "Credenciais inválidas"
}
},
"validation": {
"invalid-credentials": "Credenciais inválidas"
},
"change-email": {
"mutation": {
"pending": "Alterando e-mail...",
"success": "E-mail alterado com sucesso!"
},
"form": {
"password": "Senha atual",
"email": "Novo e-mail",
"submit": "Alterar e-mail"
}
},
"change-password": {
"mutation": {
"pending": "Alterando senha...",
"success": "Senha alterada com sucesso!"
},
"form": {
"password": "Senha atual",
"newPassword": "Nova Senha",
"newPasswordConfirm": "Confirmar nova senha",
"submit": "Alterar a senha"
}
},
"sign-in": {
"modal": {
"title": "Entrar",
"description": "Que bom ver você de novo! Vamos fazer seu login."
},
"mutation": {
"pending": "Fazendo login...",
"success": "Conectado com sucesso, redirecionando agora"
},
"form": {
"password": "Senha",
"newPassword": "Nova Senha",
"newPasswordConfirm": "Confirmar nova senha",
"submit": "Entrar",
"login": "Nome de usuário"
},
"links": {
"forgot-password": "Esqueceu sua senha?",
"register": "Ainda não tem uma conta?",
"sign-up": "Ainda não tem uma conta?"
}
},
"sign-up": {
"modal": {
"title": "Entrar",
"description": "Que bom ver você de novo! Vamos fazer seu login."
},
"mutation": {
"pending": "Inscrevendo-se...",
"success": "Inscrito com sucesso, redirecionando agora"
},
"form": {
"password": "Senha",
"newPassword": "Nova Senha",
"newPasswordConfirm": "Confirmar nova senha",
"submit": "Inscrever-se",
"login": "Nome de usuário",
"email": "E-mail",
"socialId": "Código de exclusão de caractere"
},
"links": {
"sign-in": "Já tem uma conta?"
}
},
"forgot-password": {
"mutation": {
"pending": "Enviando e-mail de redefinição de senha...",
"success": "E-mail de redefinição de senha enviado com sucesso"
},
"form": {
"email": "E-mail",
"submit": "Recuperar senha"
}
},
"reset-password": {
"mutation": {
"pending": "Alterando senha...",
"success": "Senha alterada com sucesso"
},
"form": {
"email": "E-mail",
"password": "Nova Senha",
"submit": "Redefinir senha"
}
}
}

125
messages/ro.json Normal file
View File

@ -0,0 +1,125 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"example_message": "Salut {username}",
"navigation": {
"home": "Acasă",
"rankings": "Clasamente",
"player-rankings": "Clasamentele jucătorilor",
"guild-rankings": "Clasamentele breslelor",
"download": "Descărcare",
"support": "Sprijin",
"sign-in": "Conectare",
"toggle-language": "Schimbați limba",
"my-account": "Contul meu",
"profile": "Profil",
"sign-out": "Sign out"
},
"languages": {
"title": "Limbi",
"en": "Engleză",
"pt": "Portugheză",
"de": "german",
"ro": "Română"
},
"errors": {
"unauthorized": "Nu aveți autorizație pentru a vizualiza această resursă",
"unprocessable-entity": "Nu am putut valida solicitarea dvs.",
"server-error": "Oops... Am avut o problemă de partea noastră."
},
"auth": {
"validation": {
"invalid-credentials": "Acreditări nevalide"
}
},
"validation": {
"invalid-credentials": "Acreditări nevalide"
},
"change-email": {
"mutation": {
"pending": "Schimbarea adresei de e-mail...",
"success": "Adresa de e-mail a fost schimbată cu succes!"
},
"form": {
"password": "Parola actuală",
"email": "E-mail nou",
"submit": "Schimbați adresa de e-mail"
}
},
"change-password": {
"mutation": {
"pending": "Schimbarea parolei...",
"success": "Parola a fost schimbată cu succes!"
},
"form": {
"password": "Parola actuală",
"newPassword": "Parolă Nouă",
"newPasswordConfirm": "Confirmați noua parolă",
"submit": "Schimbaţi parola"
}
},
"sign-in": {
"modal": {
"title": "Conectare",
"description": "Mă bucur să te revăd! Hai să te conectăm."
},
"mutation": {
"pending": "Conectare...",
"success": "Conectat cu succes, redirecționez acum"
},
"form": {
"password": "Parolă",
"newPassword": "Parolă Nouă",
"newPasswordConfirm": "Confirmați noua parolă",
"submit": "Conectare",
"login": "Nume de utilizator"
},
"links": {
"forgot-password": "Ați uitat parola?",
"register": "Nu ai încă un cont?",
"sign-up": "Nu ai încă un cont?"
}
},
"sign-up": {
"modal": {
"title": "Conectare",
"description": "Mă bucur să te revăd! Hai să te conectăm."
},
"mutation": {
"pending": "Înscriere...",
"success": "Înregistrare reușită, redirecționez acum"
},
"form": {
"password": "Parolă",
"newPassword": "Parolă Nouă",
"newPasswordConfirm": "Confirmați noua parolă",
"submit": "Înscrie-te",
"login": "Nume de utilizator",
"email": "E-mail",
"socialId": "Cod de ștergere a caracterelor"
},
"links": {
"sign-in": "Ai deja un cont?"
}
},
"forgot-password": {
"mutation": {
"pending": "Se trimite e-mailul de resetare a parolei...",
"success": "E-mailul de resetare a parolei a fost trimis cu succes"
},
"form": {
"email": "E-mail",
"submit": "Recuperează parola"
}
},
"reset-password": {
"mutation": {
"pending": "Schimbarea parolei...",
"success": "Parola a fost schimbată cu succes"
},
"form": {
"email": "E-mail",
"password": "Parolă Nouă",
"submit": "Resetare parolă"
}
}
}

View File

@ -10,7 +10,8 @@
"lint": "biome lint",
"email:dev": "email dev --dir ./src/emails --port 3030",
"db:generate": "drizzle-kit generate",
"i18n:translate": "inlang machine translate --project ./project.inlang",
"db:push": "bunx --bun drizzle-kit push",
"i18n:translate": "inlang machine translate --project project.inlang",
"i18n:check": "inlang validate --project ./project.inlang"
},
"dependencies": {
@ -45,11 +46,11 @@
"@radix-ui/react-tooltip": "^1.2.7",
"@react-email/components": "0.0.41",
"@tailwindcss/postcss": "^4.1.8",
"@tanstack/react-query": "^5.80.3",
"@tanstack/react-query-devtools": "^5.80.3",
"@tanstack/react-router": "^1.120.15",
"@tanstack/react-router-devtools": "^1.120.15",
"@tanstack/react-start": "^1.120.15",
"@tanstack/react-query": "^5.80.6",
"@tanstack/react-query-devtools": "^5.80.6",
"@tanstack/react-router": "^1.120.16",
"@tanstack/react-router-devtools": "^1.120.16",
"@tanstack/react-start": "^1.120.16",
"@tanstack/react-table": "^8.21.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@ -78,7 +79,7 @@
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@inlang/cli": "^3.0.11",
"@inlang/paraglide-js": "^2.0.13",
"@inlang/paraglide-js": "2.0.13",
"@tanstack/eslint-plugin-query": "^5.78.0",
"@types/bun": "^1.2.15",
"@types/nodemailer": "^6.4.17",

View File

@ -1 +1 @@
O51G4WNvcv5kcok0DT
HVTPHNyQuA8SllpEYW

View File

@ -3,13 +3,15 @@
"baseLocale": "en",
"locales": [
"en",
"pt"
"pt",
"de",
"ro"
],
"modules": [
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js",
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js"
],
"plugin.inlang.messageFormat": {
"pathPattern": "./src/translations/{locale}.json"
"pathPattern": "./messages/{locale}.json"
}
}

View File

@ -10,6 +10,7 @@ import {
import { Input } from '@/components/ui/input';
import { login, LoginSchema } from '@/lib/actions/auth';
import { isValidationError } from '@/lib/utils/http';
import { m } from '@/paraglide/messages';
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';
import { useMutation } from '@tanstack/react-query';
import { Link } from '@tanstack/react-router';
@ -31,11 +32,11 @@ export const LoginForm = () => {
await serverFn({ data: payload }),
onMutate: () => {
return {
toastId: toast.loading('Signing in...'),
toastId: toast.loading(m['sign-in.mutation.pending']()),
};
},
onSuccess: (_data, _variables, context) => {
toast.success('Signed in successfully, redirecting now', {
toast.success(m['sign-in.mutation.success'](), {
id: context.toastId,
});
},
@ -65,9 +66,13 @@ export const LoginForm = () => {
name="login"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormLabel>{m['sign-in.form.login']()}</FormLabel>
<FormControl>
<Input placeholder="Username" {...field} autoComplete="login" />
<Input
placeholder={m['sign-in.form.login']()}
{...field}
autoComplete="login"
/>
</FormControl>
<FormMessage />
</FormItem>
@ -78,9 +83,13 @@ export const LoginForm = () => {
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormLabel>{m['sign-in.form.password']()}</FormLabel>
<FormControl>
<Input placeholder="Password" type="password" {...field} />
<Input
placeholder={m['sign-in.form.password']()}
type="password"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
@ -88,17 +97,17 @@ export const LoginForm = () => {
/>
<div className="grid gap-0.5">
<Link to="/forgot-password" className="text-sm hover:underline">
Forgot your password?
{m['sign-in.links.forgot-password']()}
</Link>
<Link to="/register" className="text-sm hover:underline">
Don&apos;t have an account yet?
<Link to="/sign-up" className="text-sm hover:underline">
{m['sign-in.links.sign-up']()}
</Link>
</div>
<Button type="submit" disabled={mutation.isPending}>
{mutation.isPending && (
<LoaderCircleIcon className="size-4 animate-spin" />
)}
Login
{m['sign-in.form.submit']()}
</Button>
</form>
</Form>

View File

@ -11,28 +11,42 @@ const relations = defineRelations(schema, (t) => ({
sessions: t.many.session({
from: t.account.id,
to: t.session.userId,
alias: 'account_sessions'
alias: 'account_sessions',
}),
passwordResetSessions: t.many.passwordResetSession({
from: t.account.id,
to: t.passwordResetSession.userId,
alias: 'account_password_reset_sessions'
})
alias: 'account_password_reset_sessions',
}),
},
session: {
user: t.one.account({
from: t.session.userId,
to: t.account.id,
alias: 'session_account'
})
alias: 'session_account',
}),
},
passwordResetSession: {
user: t.one.account({
from: t.passwordResetSession.userId,
to: t.account.id,
alias: 'password_reset_session_account'
})
}
alias: 'password_reset_session_account',
}),
},
ticket: {
comments: t.many.ticketComment({
from: t.ticket.id,
to: t.ticketComment.ticketId,
alias: 'ticket_comments',
}),
},
ticketComments: {
attachments: t.many.ticketAttachment({
from: t.ticketComment.id,
to: t.ticketAttachment.ticketCommentId,
alias: 'ticket_comment_attachments',
}),
},
}));
export default relations;

View File

@ -118,7 +118,9 @@ export const goldLog = logSchema.table(
export const hackLog = logSchema.table('hack_log', {
id: integer().primaryKey().generatedAlwaysAsIdentity(),
login: char({ length: 16 }).notNull(),
login: varchar({ length: 30 })
.notNull()
.references(() => account.login),
name: char({ length: 24 }).notNull(),
ip: char({ length: 15 }).notNull(),
server: char({ length: 100 }).notNull(),

View File

@ -125,7 +125,9 @@ export const guildMember = playerSchema.table(
);
export const guildWarBet = playerSchema.table('guild_war_bet', {
login: varchar({ length: 24 }).notNull(), // TODO Figure this out
login: varchar({ length: 30 })
.notNull()
.references(() => account.login),
gold: integer().default(0).notNull(),
guildId: integer('guild_id')
.notNull()
@ -436,7 +438,9 @@ export const itemAward = playerSchema.table(
playerId: integer('player_id')
.notNull()
.references(() => player.id),
login: varchar({ length: 30 }).notNull(), // TODO Upgrade this to point directly
login: varchar({ length: 30 })
.notNull()
.references(() => account.login),
// Should point at item_proto
vnum: integer().default(0).notNull(),
count: integer().default(0).notNull(),

View File

@ -47,17 +47,6 @@ export const ticketCategory = webSchema.table('ticket_category', {
publishedAt: timestamp('published_at'),
});
const AttachmentType = ['image', 'video', 'document', 'other'];
interface AttachmentInterface {
preview?: string;
size: number;
name: string;
type: (typeof AttachmentType)[number];
url: string;
}
// TODO Fully figure out the attachment setup
export const ticket = webSchema.table('ticket', {
id: integer().primaryKey().generatedByDefaultAsIdentity(),
userId: integer()
@ -67,8 +56,6 @@ export const ticket = webSchema.table('ticket', {
.notNull()
.references(() => ticketCategory.id),
title: text().notNull(),
details: text().notNull(),
attachments: jsonb().notNull().default([]).$type<AttachmentInterface[]>(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at'),
closedAt: timestamp('closed_at'),
@ -83,7 +70,25 @@ export const ticketComment = webSchema.table('ticket_comment', {
.notNull()
.references(() => ticket.id),
comment: text().notNull(),
attachments: jsonb().notNull().default([]).$type<AttachmentInterface[]>(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at'),
});
export const ticketAttachmentType = webSchema.enum('ticket_attachment_type', [
'image',
'video',
'document',
'other',
]);
export const ticketAttachment = webSchema.table('ticket_attachment', {
id: integer().primaryKey().generatedByDefaultAsIdentity(),
ticketCommentId: integer('ticket_comment_id')
.notNull()
.references(() => ticketComment.id),
preview: jsonb(),
type: ticketAttachmentType().notNull().default('other'),
size: integer().notNull(),
name: text().notNull(),
url: text().notNull(),
});

View File

@ -19,6 +19,7 @@ import {
unauthorizedError,
validationError,
} from '../utils/http';
import { m } from '@/paraglide/messages';
export const fetchUser = createServerFn({
method: 'GET',
@ -110,13 +111,13 @@ export const login = createServerFn({
if (!user) {
throw validationError({
login: ['Invalid credentials'],
login: [m['validation.invalid-credentials']()],
});
}
if (!(await password.verify(data.password, user.password, 'argon2id'))) {
throw validationError({
login: ['Invalid credentials'],
login: [m['validation.invalid-credentials']()],
});
}

View File

@ -18,6 +18,7 @@ import {
serverError,
validationError,
} from '../utils/http';
import { m } from '@/paraglide/messages';
export const ForgotPasswordSchema = createInsertSchema(passwordResetSession)
.pick({
@ -45,7 +46,7 @@ export const forgotPassword = createServerFn({
if (!user) {
throw validationError({
email: ['Invalid email'],
email: [m['validation.invalid-credentials']()],
});
}

View File

@ -11,6 +11,7 @@ import {
unauthorizedError,
validationError,
} from '../utils/http';
import { m } from '@/paraglide/messages';
export const ChangePasswordSchema = createInsertSchema(account)
.pick({
@ -56,7 +57,7 @@ export const changePassword = createServerFn({
if (!(await password.verify(data.password, session.user.password))) {
throw validationError({
password: ['Wrong password'],
password: [m['validation.invalid-credentials']()],
});
}
@ -68,7 +69,7 @@ export const changePassword = createServerFn({
.where(eq(account.id, session.user.id));
return {
message: 'Successfully updated password',
message: m['change-password.mutation.success'](),
};
});
@ -108,7 +109,7 @@ export const changeEmail = createServerFn({
if (!(await password.verify(data.password, session.user.password))) {
throw validationError({
password: ['Wrong password'],
password: [m['validation.invalid-credentials']()],
});
}
@ -120,6 +121,6 @@ export const changeEmail = createServerFn({
.where(eq(account.id, session.user.id));
return {
message: 'Successfully updated email',
message: m['change-email.mutation.success'](),
};
});

View File

@ -1,6 +1,16 @@
import { createServerFn } from '@tanstack/react-start';
import { m } from '@/paraglide/messages';
import { useAppSession } from '../utils/session';
import { validateSessionToken } from '../session/auth';
import { getIp } from '../utils';
export const helloWorld = createServerFn().handler(() => {
return m.server_message('hello');
export const helloWorld = createServerFn().handler(async () => {
const appSession = await useAppSession();
const { user } = await validateSessionToken(
appSession.data.sessionToken,
getIp(),
);
return m.example_message({ username: user?.login || 'Guest' });
});

View File

@ -6,18 +6,36 @@ import {
overwriteGetLocale,
} from '@/paraglide/runtime';
import { resolveLocale } from './resolve-locale';
import { z } from 'zod/v4';
// TODO Remove `@ts-ignore` when zod does not have the romanian locale implemented yet
export const localeMiddleware = createMiddleware()
.client(async (context) =>
context.next({
.client(async (context) => {
const locale = await resolveLocale();
// @ts-ignore
if (z.locales[locale]) {
// @ts-ignore
z.config(z.locales[locale]());
}
return await context.next({
sendContext: {
locale: await resolveLocale(),
locale,
},
}),
)
});
})
.server(async (context) => {
const storage = new AsyncLocalStorage<Locale>();
overwriteGetLocale(() => storage.getStore() ?? baseLocale);
return await storage.run(context.context.locale, context.next);
const { locale } = context.context;
// @ts-ignore
if (z.locales[locale]) {
// @ts-ignore
z.config(z.locales[locale]());
}
return await storage.run(locale, context.next);
});

View File

@ -1,19 +1,22 @@
import { m } from '@/paraglide/messages';
import { json } from '@tanstack/react-start';
import type { z } from 'zod/v4';
import { z } from 'zod/v4';
export const unauthorizedError = () => {
return json(
{
message: 'Unauthorized',
message: m['errors.unauthorized'](),
},
{ status: 401 },
);
};
export const validationError = (errors: Record<string, string[]>) => {
type Errors = { [x: string]: string[] | undefined };
export const validationError = (errors: Errors) => {
return json(
{
message: 'There was an issue with the data provided',
message: m['errors.unprocessable-entity'](),
errors,
},
{ status: 422 },
@ -23,18 +26,18 @@ export const validationError = (errors: Record<string, string[]>) => {
export const serverError = () => {
return json(
{
message: 'Server error',
message: m['errors.server-error'](),
},
{ status: 500 },
);
};
type ValidationError<T = { [x: string]: unknown }> = Error & {
type ValidationError<T = Errors> = Error & {
message: string;
errors: Record<keyof T, string[]>;
};
export function isValidationError<T = { [x: string]: unknown }>(
export function isValidationError<T = Errors>(
error: Error,
): error is ValidationError<T> {
return Object.hasOwn(error, 'errors');
@ -47,13 +50,7 @@ export function handleServerFnValidation<T extends z.ZodSchema<unknown>>(
const result = schema.safeParse(payload);
if (!result.success) {
throw json(
{
message: 'Validation error',
errors: result.error.flatten().fieldErrors,
},
{ status: 422 },
);
throw validationError(z.flattenError(result.error).fieldErrors);
}
return result.data;

View File

@ -11,27 +11,37 @@
// Import Routes
import { Route as rootRoute } from './routes/__root'
import { Route as SignUpImport } from './routes/sign-up'
import { Route as SignInImport } from './routes/sign-in'
import { Route as ResetPasswordImport } from './routes/reset-password'
import { Route as RegisterImport } from './routes/register'
import { Route as ProfileImport } from './routes/profile'
import { Route as LoginImport } from './routes/login'
import { Route as ForgotPasswordImport } from './routes/forgot-password'
import { Route as DownloadImport } from './routes/download'
import { Route as IndexImport } from './routes/index'
import { Route as SupportIndexImport } from './routes/support/index'
import { Route as ProfileIndexImport } from './routes/profile/index'
import { Route as RankingsPlayersImport } from './routes/rankings/players'
import { Route as RankingsGuildsImport } from './routes/rankings/guilds'
import { Route as ProfileChangePasswordImport } from './routes/profile/change-password'
import { Route as ProfileChangeEmailImport } from './routes/profile/change-email'
// Create/Update Routes
const ResetPasswordRoute = ResetPasswordImport.update({
id: '/reset-password',
path: '/reset-password',
const SignUpRoute = SignUpImport.update({
id: '/sign-up',
path: '/sign-up',
getParentRoute: () => rootRoute,
} as any)
const RegisterRoute = RegisterImport.update({
id: '/register',
path: '/register',
const SignInRoute = SignInImport.update({
id: '/sign-in',
path: '/sign-in',
getParentRoute: () => rootRoute,
} as any)
const ResetPasswordRoute = ResetPasswordImport.update({
id: '/reset-password',
path: '/reset-password',
getParentRoute: () => rootRoute,
} as any)
@ -41,30 +51,48 @@ const ProfileRoute = ProfileImport.update({
getParentRoute: () => rootRoute,
} as any)
const LoginRoute = LoginImport.update({
id: '/login',
path: '/login',
getParentRoute: () => rootRoute,
} as any)
const ForgotPasswordRoute = ForgotPasswordImport.update({
id: '/forgot-password',
path: '/forgot-password',
getParentRoute: () => rootRoute,
} as any)
const DownloadRoute = DownloadImport.update({
id: '/download',
path: '/download',
getParentRoute: () => rootRoute,
} as any)
const IndexRoute = IndexImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRoute,
} as any)
const SupportIndexRoute = SupportIndexImport.update({
id: '/support/',
path: '/support/',
getParentRoute: () => rootRoute,
} as any)
const ProfileIndexRoute = ProfileIndexImport.update({
id: '/',
path: '/',
getParentRoute: () => ProfileRoute,
} as any)
const RankingsPlayersRoute = RankingsPlayersImport.update({
id: '/rankings/players',
path: '/rankings/players',
getParentRoute: () => rootRoute,
} as any)
const RankingsGuildsRoute = RankingsGuildsImport.update({
id: '/rankings/guilds',
path: '/rankings/guilds',
getParentRoute: () => rootRoute,
} as any)
const ProfileChangePasswordRoute = ProfileChangePasswordImport.update({
id: '/change-password',
path: '/change-password',
@ -88,6 +116,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexImport
parentRoute: typeof rootRoute
}
'/download': {
id: '/download'
path: '/download'
fullPath: '/download'
preLoaderRoute: typeof DownloadImport
parentRoute: typeof rootRoute
}
'/forgot-password': {
id: '/forgot-password'
path: '/forgot-password'
@ -95,13 +130,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ForgotPasswordImport
parentRoute: typeof rootRoute
}
'/login': {
id: '/login'
path: '/login'
fullPath: '/login'
preLoaderRoute: typeof LoginImport
parentRoute: typeof rootRoute
}
'/profile': {
id: '/profile'
path: '/profile'
@ -109,13 +137,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ProfileImport
parentRoute: typeof rootRoute
}
'/register': {
id: '/register'
path: '/register'
fullPath: '/register'
preLoaderRoute: typeof RegisterImport
parentRoute: typeof rootRoute
}
'/reset-password': {
id: '/reset-password'
path: '/reset-password'
@ -123,6 +144,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ResetPasswordImport
parentRoute: typeof rootRoute
}
'/sign-in': {
id: '/sign-in'
path: '/sign-in'
fullPath: '/sign-in'
preLoaderRoute: typeof SignInImport
parentRoute: typeof rootRoute
}
'/sign-up': {
id: '/sign-up'
path: '/sign-up'
fullPath: '/sign-up'
preLoaderRoute: typeof SignUpImport
parentRoute: typeof rootRoute
}
'/profile/change-email': {
id: '/profile/change-email'
path: '/change-email'
@ -137,6 +172,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ProfileChangePasswordImport
parentRoute: typeof ProfileImport
}
'/rankings/guilds': {
id: '/rankings/guilds'
path: '/rankings/guilds'
fullPath: '/rankings/guilds'
preLoaderRoute: typeof RankingsGuildsImport
parentRoute: typeof rootRoute
}
'/rankings/players': {
id: '/rankings/players'
path: '/rankings/players'
fullPath: '/rankings/players'
preLoaderRoute: typeof RankingsPlayersImport
parentRoute: typeof rootRoute
}
'/profile/': {
id: '/profile/'
path: '/'
@ -144,6 +193,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ProfileIndexImport
parentRoute: typeof ProfileImport
}
'/support/': {
id: '/support/'
path: '/support'
fullPath: '/support'
preLoaderRoute: typeof SupportIndexImport
parentRoute: typeof rootRoute
}
}
}
@ -166,92 +222,124 @@ const ProfileRouteWithChildren =
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/download': typeof DownloadRoute
'/forgot-password': typeof ForgotPasswordRoute
'/login': typeof LoginRoute
'/profile': typeof ProfileRouteWithChildren
'/register': typeof RegisterRoute
'/reset-password': typeof ResetPasswordRoute
'/sign-in': typeof SignInRoute
'/sign-up': typeof SignUpRoute
'/profile/change-email': typeof ProfileChangeEmailRoute
'/profile/change-password': typeof ProfileChangePasswordRoute
'/rankings/guilds': typeof RankingsGuildsRoute
'/rankings/players': typeof RankingsPlayersRoute
'/profile/': typeof ProfileIndexRoute
'/support': typeof SupportIndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/download': typeof DownloadRoute
'/forgot-password': typeof ForgotPasswordRoute
'/login': typeof LoginRoute
'/register': typeof RegisterRoute
'/reset-password': typeof ResetPasswordRoute
'/sign-in': typeof SignInRoute
'/sign-up': typeof SignUpRoute
'/profile/change-email': typeof ProfileChangeEmailRoute
'/profile/change-password': typeof ProfileChangePasswordRoute
'/rankings/guilds': typeof RankingsGuildsRoute
'/rankings/players': typeof RankingsPlayersRoute
'/profile': typeof ProfileIndexRoute
'/support': typeof SupportIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexRoute
'/download': typeof DownloadRoute
'/forgot-password': typeof ForgotPasswordRoute
'/login': typeof LoginRoute
'/profile': typeof ProfileRouteWithChildren
'/register': typeof RegisterRoute
'/reset-password': typeof ResetPasswordRoute
'/sign-in': typeof SignInRoute
'/sign-up': typeof SignUpRoute
'/profile/change-email': typeof ProfileChangeEmailRoute
'/profile/change-password': typeof ProfileChangePasswordRoute
'/rankings/guilds': typeof RankingsGuildsRoute
'/rankings/players': typeof RankingsPlayersRoute
'/profile/': typeof ProfileIndexRoute
'/support/': typeof SupportIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/download'
| '/forgot-password'
| '/login'
| '/profile'
| '/register'
| '/reset-password'
| '/sign-in'
| '/sign-up'
| '/profile/change-email'
| '/profile/change-password'
| '/rankings/guilds'
| '/rankings/players'
| '/profile/'
| '/support'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/download'
| '/forgot-password'
| '/login'
| '/register'
| '/reset-password'
| '/sign-in'
| '/sign-up'
| '/profile/change-email'
| '/profile/change-password'
| '/rankings/guilds'
| '/rankings/players'
| '/profile'
| '/support'
id:
| '__root__'
| '/'
| '/download'
| '/forgot-password'
| '/login'
| '/profile'
| '/register'
| '/reset-password'
| '/sign-in'
| '/sign-up'
| '/profile/change-email'
| '/profile/change-password'
| '/rankings/guilds'
| '/rankings/players'
| '/profile/'
| '/support/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
DownloadRoute: typeof DownloadRoute
ForgotPasswordRoute: typeof ForgotPasswordRoute
LoginRoute: typeof LoginRoute
ProfileRoute: typeof ProfileRouteWithChildren
RegisterRoute: typeof RegisterRoute
ResetPasswordRoute: typeof ResetPasswordRoute
SignInRoute: typeof SignInRoute
SignUpRoute: typeof SignUpRoute
RankingsGuildsRoute: typeof RankingsGuildsRoute
RankingsPlayersRoute: typeof RankingsPlayersRoute
SupportIndexRoute: typeof SupportIndexRoute
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
DownloadRoute: DownloadRoute,
ForgotPasswordRoute: ForgotPasswordRoute,
LoginRoute: LoginRoute,
ProfileRoute: ProfileRouteWithChildren,
RegisterRoute: RegisterRoute,
ResetPasswordRoute: ResetPasswordRoute,
SignInRoute: SignInRoute,
SignUpRoute: SignUpRoute,
RankingsGuildsRoute: RankingsGuildsRoute,
RankingsPlayersRoute: RankingsPlayersRoute,
SupportIndexRoute: SupportIndexRoute,
}
export const routeTree = rootRoute
@ -265,22 +353,26 @@ export const routeTree = rootRoute
"filePath": "__root.tsx",
"children": [
"/",
"/download",
"/forgot-password",
"/login",
"/profile",
"/register",
"/reset-password"
"/reset-password",
"/sign-in",
"/sign-up",
"/rankings/guilds",
"/rankings/players",
"/support/"
]
},
"/": {
"filePath": "index.tsx"
},
"/download": {
"filePath": "download.tsx"
},
"/forgot-password": {
"filePath": "forgot-password.tsx"
},
"/login": {
"filePath": "login.tsx"
},
"/profile": {
"filePath": "profile.tsx",
"children": [
@ -289,12 +381,15 @@ export const routeTree = rootRoute
"/profile/"
]
},
"/register": {
"filePath": "register.tsx"
},
"/reset-password": {
"filePath": "reset-password.tsx"
},
"/sign-in": {
"filePath": "sign-in.tsx"
},
"/sign-up": {
"filePath": "sign-up.tsx"
},
"/profile/change-email": {
"filePath": "profile/change-email.tsx",
"parent": "/profile"
@ -303,9 +398,18 @@ export const routeTree = rootRoute
"filePath": "profile/change-password.tsx",
"parent": "/profile"
},
"/rankings/guilds": {
"filePath": "rankings/guilds.tsx"
},
"/rankings/players": {
"filePath": "rankings/players.tsx"
},
"/profile/": {
"filePath": "profile/index.tsx",
"parent": "/profile"
},
"/support/": {
"filePath": "support/index.tsx"
}
}
}

View File

@ -1,4 +1,3 @@
import '../i18n';
import {
HeadContent,
Link,
@ -34,6 +33,8 @@ import {
import { useServerFn } from '@tanstack/react-start';
import { LoginForm } from '@/components/login/form';
import { Toaster } from '@/components/ui/sonner';
import { m } from '@/paraglide/messages';
import { getLocale, locales, setLocale } from '@/paraglide/runtime';
export const Route = createRootRoute({
head: () => ({
@ -102,38 +103,45 @@ const queryClient = new QueryClient();
function RootComponent() {
const { user } = Route.useRouteContext();
const doLogout = useServerFn(logout);
const currentLocale = getLocale();
return (
<RootDocument>
<QueryClientProvider client={queryClient}>
<nav className="bg-muted">
<div className="container flex flex-row *:px-4 *:py-3 *:flex">
<Link to="/">Home</Link>
<Link to="/">{m['navigation.home']()}</Link>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button type="button">Rankings</button>
<button type="button">{m['navigation.rankings']()}</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>
<Link to="/rankings/players">Player Ranking</Link>
<Link to="/rankings/players">
{m['navigation.player-rankings']()}
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/rankings/guilds">Guild Ranking</Link>
<Link to="/rankings/guilds">
{m['navigation.guild-rankings']}
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Link to="/download">Download</Link>
<Link to="/support">Support</Link>
<Link to="/download">{m['navigation.download']()}</Link>
<Link to="/support">{m['navigation.support']()}</Link>
{user ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button type="button">{user.login}</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuLabel>
{m['navigation.my-account']()}
</DropdownMenuLabel>
<DropdownMenuGroup>
<DropdownMenuItem asChild>
<Link to="/profile">Profile</Link>
<Link to="/profile">{m['navigation.profile']()}</Link>
</DropdownMenuItem>
<form
onSubmit={async (event) => {
@ -143,7 +151,7 @@ function RootComponent() {
>
<DropdownMenuItem asChild>
<button type="submit" className="w-full">
Sign out
{m['navigation.sign-out']()}
</button>
</DropdownMenuItem>
</form>
@ -151,15 +159,16 @@ function RootComponent() {
</DropdownMenuContent>
</DropdownMenu>
) : (
// TODO Make dialog state dependent so it can be dismissed on navigation
<Dialog>
<DialogTrigger asChild>
<button type="button">Sign in</button>
<button type="button">{m['navigation.sign-in']()}</button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Sign in</DialogTitle>
<DialogTitle>{m['sign-in.modal.title']()}</DialogTitle>
<DialogDescription>
Good to see you again! Let&apos;s get you signed in.
{m['sign-in.modal.description']()}
</DialogDescription>
</DialogHeader>
<div className="pt-1.5">
@ -168,6 +177,30 @@ function RootComponent() {
</DialogContent>
</Dialog>
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button type="button" className="ms-auto">
{m['navigation.toggle-language']()}
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuLabel>{m['languages.title']()}</DropdownMenuLabel>
<DropdownMenuGroup>
{locales.map((locale) => (
<DropdownMenuItem asChild key={locale}>
<button
type="button"
onClick={() => setLocale(locale)}
className="w-full"
disabled={locale === currentLocale}
>
{m[`languages.${locale}`]()}
</button>
</DropdownMenuItem>
))}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</nav>
<div className="container">

View File

@ -1,28 +0,0 @@
import { json } from '@tanstack/react-start';
import { createAPIFileRoute } from '@tanstack/react-start/api';
import type { User } from '../../utils/users';
export const APIRoute = createAPIFileRoute('/api/users/$id')({
GET: async ({ request, params }) => {
console.info(`Fetching users by id=${params.id}... @`, request.url);
try {
const res = await fetch(
`https://jsonplaceholder.typicode.com/users/${params.id}`,
);
if (!res.ok) {
throw new Error('Failed to fetch user');
}
const user = (await res.json()) as User;
return json({
id: user.id,
name: user.name,
email: user.email,
});
} catch (e) {
console.error(e);
return json({ error: 'User not found' }, { status: 404 });
}
},
});

View File

@ -1,19 +0,0 @@
import { json } from '@tanstack/react-start'
import { createAPIFileRoute } from '@tanstack/react-start/api'
import type { User } from '../../utils/users'
export const APIRoute = createAPIFileRoute('/api/users')({
GET: async ({ request }) => {
console.info('Fetching users... @', request.url)
const res = await fetch('https://jsonplaceholder.typicode.com/users')
if (!res.ok) {
throw new Error('Failed to fetch users')
}
const data = (await res.json()) as Array<User>
const list = data.slice(0, 10)
return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email })))
},
})

9
src/routes/download.tsx Normal file
View File

@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/download')({
component: Page,
});
function Page() {
return <div>Hello "/download"!</div>;
}

View File

@ -9,12 +9,15 @@ import {
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { forgotPassword, ForgotPasswordSchema } from '@/lib/actions/password';
import { isValidationError } from '@/lib/utils/http';
import { m } from '@/paraglide/messages';
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';
import { useMutation } from '@tanstack/react-query';
import { createFileRoute, redirect } from '@tanstack/react-router';
import { useServerFn } from '@tanstack/react-start';
import { LoaderCircleIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import type { z } from 'zod/v4';
export const Route = createFileRoute('/forgot-password')({
@ -25,10 +28,10 @@ export const Route = createFileRoute('/forgot-password')({
});
}
},
component: RouteComponent,
component: Page,
});
function RouteComponent() {
function Page() {
const serverFn = useServerFn(forgotPassword);
const form = useForm<z.infer<typeof ForgotPasswordSchema>>({
@ -38,7 +41,30 @@ function RouteComponent() {
const mutation = useMutation({
mutationFn: async (payload: z.infer<typeof ForgotPasswordSchema>) =>
await serverFn({ data: payload }),
// TODO Sonner
onMutate: () => {
return {
toastId: toast.loading(m['forgot-password.mutation.pending']()),
};
},
onSuccess: (_data, _variables, context) => {
toast.success(m['forgot-password.mutation.success'](), {
id: context.toastId,
});
},
onError: (err, _variables, context) => {
toast.error(err.message, {
id: context?.toastId,
});
if (isValidationError<z.infer<typeof ForgotPasswordSchema>>(err)) {
for (const [key, errors] of Object.entries(err.errors) as [
keyof typeof err.errors,
string[],
][]) {
form.setError(key, { message: errors[0] });
}
}
},
});
const onSubmit = form.handleSubmit((payload) => mutation.mutate(payload));
@ -51,9 +77,13 @@ function RouteComponent() {
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormLabel>{m['forgot-password.form.email']()}</FormLabel>
<FormControl>
<Input placeholder="Email" type="email" {...field} />
<Input
placeholder={m['forgot-password.form.email']()}
type="email"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
@ -63,7 +93,7 @@ function RouteComponent() {
{mutation.isPending && (
<LoaderCircleIcon className="size-4 animate-spin" />
)}
Recover password
{m['forgot-password.form.submit']()}
</Button>
</form>
</Form>

View File

@ -1,13 +1,17 @@
import { helloWorld } from '@/lib/actions/test';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
component: Page,
loader: async () => await helloWorld(),
});
function Page() {
const message = Route.useLoaderData();
return (
<div className="py-4">
<h3>Welcome Home!!!</h3>
<h3>{message}</h3>
</div>
);
}

View File

@ -1,9 +1,111 @@
import { createFileRoute } from '@tanstack/react-router'
import { Button } from '@/components/ui/button';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { changeEmail, ChangeEmailSchema } from '@/lib/actions/profile';
import { isValidationError } from '@/lib/utils/http';
import { m } from '@/paraglide/messages';
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';
import { useMutation } from '@tanstack/react-query';
import { createFileRoute } from '@tanstack/react-router';
import { useServerFn } from '@tanstack/react-start';
import { LoaderCircleIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import type { z } from 'zod/v4';
export const Route = createFileRoute('/profile/change-email')({
component: Page,
})
});
function Page() {
return <div>Hello "/profile/change-email"!</div>
const serverFn = useServerFn(changeEmail);
const form = useForm<z.infer<typeof ChangeEmailSchema>>({
resolver: standardSchemaResolver(ChangeEmailSchema),
});
const mutation = useMutation({
mutationFn: async (payload: z.infer<typeof ChangeEmailSchema>) =>
await serverFn({ data: payload }),
onMutate: () => {
return {
toastId: toast.loading(m['change-email.mutation.pending']()),
};
},
onSuccess: (data, _variables, context) => {
toast.success(data.message, {
id: context.toastId,
});
},
onError: (err, _variables, context) => {
toast.error(err.message, {
id: context?.toastId,
});
if (isValidationError<z.infer<typeof ChangeEmailSchema>>(err)) {
for (const [key, errors] of Object.entries(err.errors) as [
keyof typeof err.errors,
string[],
][]) {
form.setError(key, { message: errors[0] });
}
}
},
});
const onSubmit = form.handleSubmit((payload) => mutation.mutate(payload));
return (
<Form {...form}>
<form onSubmit={onSubmit} className="grid gap-2">
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>{m['change-email.form.password']()}</FormLabel>
<FormControl>
<Input
placeholder={m['change-email.form.password']()}
type="password"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>{m['change-email.form.email']()}</FormLabel>
<FormControl>
<Input
placeholder={m['change-email.form.email']()}
type="email"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" disabled={mutation.isPending}>
{mutation.isPending && (
<LoaderCircleIcon className="size-4 animate-spin" />
)}
{m['change-email.form.submit']()}
</Button>
</form>
</Form>
);
}

View File

@ -1,9 +1,130 @@
import { createFileRoute } from '@tanstack/react-router'
import { Button } from '@/components/ui/button';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { changePassword, ChangePasswordSchema } from '@/lib/actions/profile';
import { isValidationError } from '@/lib/utils/http';
import { m } from '@/paraglide/messages';
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';
import { useMutation } from '@tanstack/react-query';
import { createFileRoute } from '@tanstack/react-router';
import { useServerFn } from '@tanstack/react-start';
import { LoaderCircleIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import type { z } from 'zod/v4';
export const Route = createFileRoute('/profile/change-password')({
component: Page,
})
});
function Page() {
return <div>Hello "/profile/change-password"!</div>
const serverFn = useServerFn(changePassword);
const form = useForm<z.infer<typeof ChangePasswordSchema>>({
resolver: standardSchemaResolver(ChangePasswordSchema),
});
const mutation = useMutation({
mutationFn: async (payload: z.infer<typeof ChangePasswordSchema>) =>
await serverFn({ data: payload }),
onMutate: () => {
return {
toastId: toast.loading(m['change-password.mutation.pending']()),
};
},
onSuccess: (data, _variables, context) => {
toast.success(data.message, {
id: context.toastId,
});
},
onError: (err, _variables, context) => {
toast.error(err.message, {
id: context?.toastId,
});
if (isValidationError<z.infer<typeof ChangePasswordSchema>>(err)) {
for (const [key, errors] of Object.entries(err.errors) as [
keyof typeof err.errors,
string[],
][]) {
form.setError(key, { message: errors[0] });
}
}
},
});
const onSubmit = form.handleSubmit((payload) => mutation.mutate(payload));
return (
<Form {...form}>
<form onSubmit={onSubmit} className="grid gap-2">
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>{m['change-password.form.password']()}</FormLabel>
<FormControl>
<Input
placeholder={m['change-password.form.password']()}
type="password"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="newPassword"
render={({ field }) => (
<FormItem>
<FormLabel>{m['change-password.form.newPassword']()}</FormLabel>
<FormControl>
<Input
placeholder={m['change-password.form.newPassword']()}
type="password"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="newPasswordConfirm"
render={({ field }) => (
<FormItem>
<FormLabel>
{m['change-password.form.newPasswordConfirm']()}
</FormLabel>
<FormControl>
<Input
placeholder={m['change-password.form.newPasswordConfirm']()}
type="password"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" disabled={mutation.isPending}>
{mutation.isPending && (
<LoaderCircleIcon className="size-4 animate-spin" />
)}
{m['change-password.form.submit']()}
</Button>
</form>
</Form>
);
}

View File

@ -1,9 +1,10 @@
import { createFileRoute } from '@tanstack/react-router';
import { createFileRoute, Link } from '@tanstack/react-router';
export const Route = createFileRoute('/profile/')({
component: Page,
});
// TODO Structure
function Page() {
const { user } = Route.useRouteContext();
@ -11,5 +12,17 @@ function Page() {
return null;
}
return <div>Hello {user.login}</div>;
return (
<div>
Hello {user.login}
<ul>
<li>
<Link to="/profile/change-email">Change email</Link>
</li>
<li>
<Link to="/profile/change-password">Change password</Link>
</li>
</ul>
</div>
);
}

View File

@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/rankings/guilds')({
component: Page,
});
function Page() {
return <div>Hello "/rankings/guilds"!</div>;
}

View File

@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/rankings/players')({
component: Page,
});
function Page() {
return <div>Hello "/rankings/players"!</div>;
}

View File

@ -14,12 +14,15 @@ import {
ResetPasswordSchema,
validateResetPassword,
} from '@/lib/actions/password';
import { isValidationError } from '@/lib/utils/http';
import { m } from '@/paraglide/messages';
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';
import { useMutation } from '@tanstack/react-query';
import { createFileRoute, redirect } from '@tanstack/react-router';
import { useServerFn } from '@tanstack/react-start';
import { LoaderCircleIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import type { z } from 'zod/v4';
export const Route = createFileRoute('/reset-password')({
@ -56,7 +59,30 @@ function Page() {
const mutation = useMutation({
mutationFn: async (payload: z.infer<typeof ResetPasswordActionSchema>) =>
await serverFn({ data: payload }),
// TODO Sonner
onMutate: () => {
return {
toastId: toast.loading(m['reset-password.mutation.pending']()),
};
},
onSuccess: (_data, _variables, context) => {
toast.success(m['reset-password.mutation.success'](), {
id: context.toastId,
});
},
onError: (err, _variables, context) => {
toast.error(err.message, {
id: context?.toastId,
});
if (isValidationError<z.infer<typeof ResetPasswordActionSchema>>(err)) {
for (const [key, errors] of Object.entries(err.errors) as [
keyof typeof err.errors,
string[],
][]) {
form.setError(key, { message: errors[0] });
}
}
},
});
const onSubmit = form.handleSubmit((payload) => mutation.mutate(payload));
@ -69,9 +95,13 @@ function Page() {
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormLabel>{m['reset-password.form.email']()}</FormLabel>
<FormControl>
<Input placeholder="Email" {...field} readOnly />
<Input
placeholder={m['reset-password.form.email']()}
{...field}
readOnly
/>
</FormControl>
<FormMessage />
</FormItem>
@ -82,9 +112,13 @@ function Page() {
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormLabel>{m['reset-password.form.password']()}</FormLabel>
<FormControl>
<Input placeholder="Password" type="password" {...field} />
<Input
placeholder={m['reset-password.form.password']()}
type="password"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
@ -94,7 +128,7 @@ function Page() {
{mutation.isPending && (
<LoaderCircleIcon className="size-4 animate-spin" />
)}
Reset Password
{m['reset-password.form.submit']()}
</Button>
</form>
</Form>

View File

@ -1,7 +1,7 @@
import { LoginForm } from '@/components/login/form';
import { createFileRoute, redirect } from '@tanstack/react-router';
export const Route = createFileRoute('/login')({
export const Route = createFileRoute('/sign-in')({
beforeLoad: ({ context }) => {
if (context.user) {
throw redirect({ href: '/' });

View File

@ -1,4 +1,4 @@
import { createFileRoute, redirect } from '@tanstack/react-router';
import { createFileRoute, Link, redirect } from '@tanstack/react-router';
import { useForm } from 'react-hook-form';
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';
import {
@ -18,8 +18,9 @@ import type { z } from 'zod/v4';
import { register, RegisterSchema } from '@/lib/actions/auth';
import { toast } from 'sonner';
import { isValidationError } from '@/lib/utils/http';
import { m } from '@/paraglide/messages';
export const Route = createFileRoute('/register')({
export const Route = createFileRoute('/sign-up')({
beforeLoad: ({ context }) => {
if (context.user) {
throw redirect({ href: '/' });
@ -40,11 +41,11 @@ function Page() {
await serverFn({ data: payload }),
onMutate: () => {
return {
toastId: toast.loading('Signing up...'),
toastId: toast.loading(m['sign-up.mutation.pending']()),
};
},
onSuccess: (_data, _variables, context) => {
toast.success('Signed up successfully, redirecting now', {
toast.success(m['sign-up.mutation.success'](), {
id: context.toastId,
});
},
@ -74,9 +75,9 @@ function Page() {
name="login"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormLabel>{m['sign-up.form.login']()}</FormLabel>
<FormControl>
<Input placeholder="Username" {...field} />
<Input placeholder={m['sign-up.form.login']()} {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -87,9 +88,13 @@ function Page() {
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormLabel>{m['sign-up.form.email']()}</FormLabel>
<FormControl>
<Input placeholder="Email" type="email" {...field} />
<Input
placeholder={m['sign-up.form.email']()}
type="email"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
@ -100,9 +105,9 @@ function Page() {
name="socialId"
render={({ field }) => (
<FormItem>
<FormLabel>Character deletion code</FormLabel>
<FormLabel>{m['sign-up.form.socialId']()}</FormLabel>
<FormControl>
<Input placeholder="Character deletion code" {...field} />
<Input placeholder={m['sign-up.form.socialId']()} {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -113,19 +118,28 @@ function Page() {
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormLabel>{m['sign-up.form.password']()}</FormLabel>
<FormControl>
<Input placeholder="Password" type="password" {...field} />
<Input
placeholder={m['sign-up.form.password']()}
type="password"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="grid gap-0.5">
<Link to="/sign-in" className="text-sm hover:underline">
{m['sign-up.links.sign-in']()}
</Link>
</div>
<Button type="submit" disabled={mutation.isPending}>
{mutation.isPending && (
<LoaderCircleIcon className="size-4 animate-spin" />
)}
Register
{m['sign-up.form.submit']()}
</Button>
</form>
</Form>

View File

@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/support/')({
component: Page,
});
function Page() {
return <div>Hello "/support/"!</div>;
}