彻底重构-1
This commit is contained in:
@@ -6,7 +6,10 @@ CADDY_SITE_ADDR=http://926d-193-176-84-20.ngrok-free.app
|
|||||||
TZ=Asia/Shanghai
|
TZ=Asia/Shanghai
|
||||||
BACKEND_PORT=3000
|
BACKEND_PORT=3000
|
||||||
CLAIM_BASE_URL=https://926d-193-176-84-20.ngrok-free.app/#/claim
|
CLAIM_BASE_URL=https://926d-193-176-84-20.ngrok-free.app/#/claim
|
||||||
DATABASE_FILE_PATH=/app/data/order-site.db
|
DATABASE_URL=postgres://postgres:postgres@postgres:5432/order_site
|
||||||
|
DATABASE_SSL=false
|
||||||
|
DATABASE_MAX_CONNECTIONS=10
|
||||||
|
DATA_ROOT=/app/data
|
||||||
OCR_PROJECT_ROOT=/app/subservices/ocr-worker
|
OCR_PROJECT_ROOT=/app/subservices/ocr-worker
|
||||||
TENCENT_REDEEM_PROOF_MODE=basic
|
TENCENT_REDEEM_PROOF_MODE=basic
|
||||||
TENCENT_BROWSER_HEADLESS=true
|
TENCENT_BROWSER_HEADLESS=true
|
||||||
@@ -20,6 +23,7 @@ ADMIN_SESSION_SECRET=dev-local-session-secret
|
|||||||
ADMIN_DEFAULT_USERS_JSON=[{"username":"admin","password":"dev-admin-123456","role":"admin"},{"username":"operator","password":"dev-operator-123456","role":"operator"}]
|
ADMIN_DEFAULT_USERS_JSON=[{"username":"admin","password":"dev-admin-123456","role":"admin"},{"username":"operator","password":"dev-operator-123456","role":"operator"}]
|
||||||
|
|
||||||
ORDER_SKU_MAPPINGS_JSON={"32768":"dnf-cdk-a","dnf-cdk-a":"dnf-cdk-a"}
|
ORDER_SKU_MAPPINGS_JSON={"32768":"dnf-cdk-a","dnf-cdk-a":"dnf-cdk-a"}
|
||||||
|
ORDER_FULFILLMENT_BINDINGS_JSON=[]
|
||||||
|
|
||||||
AGISO_APP_SECRET=
|
AGISO_APP_SECRET=
|
||||||
AGISO_MESSAGING_ENABLED=false
|
AGISO_MESSAGING_ENABLED=false
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ APP_BASE_URL=https://order.example.com
|
|||||||
TZ=Asia/Shanghai
|
TZ=Asia/Shanghai
|
||||||
BACKEND_PORT=3000
|
BACKEND_PORT=3000
|
||||||
CLAIM_BASE_URL=https://order.example.com/#/claim
|
CLAIM_BASE_URL=https://order.example.com/#/claim
|
||||||
DATABASE_FILE_PATH=/app/data/order-site.db
|
DATABASE_URL=postgres://postgres:replace-with-strong-password@postgres:5432/order_site
|
||||||
|
DATABASE_SSL=false
|
||||||
|
DATABASE_MAX_CONNECTIONS=20
|
||||||
|
DATA_ROOT=/app/data
|
||||||
OCR_PROJECT_ROOT=/app/subservices/ocr-worker
|
OCR_PROJECT_ROOT=/app/subservices/ocr-worker
|
||||||
TENCENT_REDEEM_PROOF_MODE=basic
|
TENCENT_REDEEM_PROOF_MODE=basic
|
||||||
TENCENT_BROWSER_HEADLESS=true
|
TENCENT_BROWSER_HEADLESS=true
|
||||||
@@ -18,6 +21,7 @@ ADMIN_SESSION_SECRET=replace-with-a-long-random-secret
|
|||||||
ADMIN_DEFAULT_USERS_JSON=[{"username":"admin","password":"replace-with-strong-admin-password","role":"admin"},{"username":"operator","password":"replace-with-strong-operator-password","role":"operator"}]
|
ADMIN_DEFAULT_USERS_JSON=[{"username":"admin","password":"replace-with-strong-admin-password","role":"admin"},{"username":"operator","password":"replace-with-strong-operator-password","role":"operator"}]
|
||||||
|
|
||||||
ORDER_SKU_MAPPINGS_JSON={"32768":"dnf-cdk-a","dnf-cdk-a":"dnf-cdk-a"}
|
ORDER_SKU_MAPPINGS_JSON={"32768":"dnf-cdk-a","dnf-cdk-a":"dnf-cdk-a"}
|
||||||
|
ORDER_FULFILLMENT_BINDINGS_JSON=[]
|
||||||
|
|
||||||
AGISO_APP_SECRET=
|
AGISO_APP_SECRET=
|
||||||
AGISO_MESSAGING_ENABLED=false
|
AGISO_MESSAGING_ENABLED=false
|
||||||
|
|||||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
|||||||
|
{"_id":"pg-int8","_rev":"2-f36a718b2d94526cee7ee0a230537717","name":"pg-int8","description":"64-bit big-endian signed integer-to-string conversion","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"pg-int8","version":"1.0.0","description":"64-bit big-endian signed integer-to-string conversion","bugs":{"url":"https://github.com/charmander/pg-int8/issues"},"license":"ISC","files":["index.js"],"repository":{"type":"git","url":"git+https://github.com/charmander/pg-int8.git"},"scripts":{"test":"tap test"},"devDependencies":{"@charmander/eslint-config-base":"1.0.2","tap":"10.7.3"},"engines":{"node":">=4.0.0"},"gitHead":"128d1fbdff339c0c92715b22c7028fb1b8bff232","homepage":"https://github.com/charmander/pg-int8#readme","_id":"pg-int8@1.0.0","_npmVersion":"5.5.1-canary.5","_nodeVersion":"9.1.0","_npmUser":{"name":"charmander","email":"~@charmander.me"},"dist":{"integrity":"sha512-5SojTTxi9rTObWGqfcsPd/+uyU4ctsmChLhqQVTzzc9ltDsWni1TJuYwX/lJMDFdGgd9lVgkXcDyQvRIgI5elw==","shasum":"d93de9931d87b24be674febc0fdaac6d52701de5","tarball":"https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.0.tgz","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCNu6nZ3IQxDxLRZD3lwlfGyjCBSb5epqnlZ+FoXSpngQIhALdjSrgD3kncz8eSbed1lhe+1hwsU+tYJ8U6WDUV4ie8"}]},"maintainers":[{"name":"charmander","email":"~@charmander.me"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/pg-int8-1.0.0.tgz_1510664927409_0.6727827792055905"}},"1.0.1":{"name":"pg-int8","version":"1.0.1","description":"64-bit big-endian signed integer-to-string conversion","bugs":{"url":"https://github.com/charmander/pg-int8/issues"},"license":"ISC","files":["index.js"],"repository":{"type":"git","url":"git+https://github.com/charmander/pg-int8.git"},"scripts":{"test":"tap test"},"devDependencies":{"@charmander/eslint-config-base":"1.0.2","tap":"10.7.3"},"engines":{"node":">=4.0.0"},"gitHead":"91d0683b3d0f9b24176bbbffbd03680cd4c97b17","homepage":"https://github.com/charmander/pg-int8#readme","_id":"pg-int8@1.0.1","_npmVersion":"5.5.1-canary.5","_nodeVersion":"9.1.0","_npmUser":{"name":"charmander","email":"~@charmander.me"},"dist":{"integrity":"sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==","shasum":"943bd463bf5b71b4170115f80f8efc9a0c0eb78c","tarball":"https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIF9L/4GuZHlpA04az3y01bx86aCmzI5RycYIs5hrv1uIAiEAwzYCQdxNcOq0t3zbFF5JZZ1FqcdthHOcbv0vQSwKh24="}]},"maintainers":[{"name":"charmander","email":"~@charmander.me"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/pg-int8-1.0.1.tgz_1510699189921_0.7000504340976477"}}},"readme":"[![Build status][ci image]][ci]\n\n64-bit big-endian signed integer-to-string conversion designed for [pg][].\n\n```js\nconst readInt8 = require('pg-int8');\n\nreadInt8(Buffer.from([0, 1, 2, 3, 4, 5, 6, 7]))\n// '283686952306183'\n```\n\n\n [pg]: https://github.com/brianc/node-postgres\n\n [ci]: https://travis-ci.org/charmander/pg-int8\n [ci image]: https://api.travis-ci.org/charmander/pg-int8.svg\n","maintainers":[{"name":"charmander","email":"~@charmander.me"}],"time":{"modified":"2022-05-12T20:28:23.225Z","created":"2017-11-14T13:08:47.528Z","1.0.0":"2017-11-14T13:08:47.528Z","1.0.1":"2017-11-14T22:39:49.993Z"},"homepage":"https://github.com/charmander/pg-int8#readme","repository":{"type":"git","url":"git+https://github.com/charmander/pg-int8.git"},"bugs":{"url":"https://github.com/charmander/pg-int8/issues"},"license":"ISC","readmeFilename":"README.md"}
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
+1
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
ef1f6f54e72963a409e389a1fbc4df7e5b8c60ab {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/postgres-bytea","integrity":"sha512-FIDhbzSPuppe1doCcgfbJwOB/1tfGlhLiJDF/DtpiKk/67m1MrZX8Dj7nqdUODXjS4gZVL2FpMrzM1ToHJ+vjg==","time":1775676221627,"size":8854,"metadata":{"time":1775676221621,"url":"https://registry.npmjs.org/postgres-bytea","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:41 GMT","etag":"W/\"0f75dbcad6c04c791e6b4cd3b9a521a7\"","last-modified":"Wed, 17 Dec 2025 04:53:35 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
5ff0da64b26a5398f1b281c6dbcdaae11f8eb9d3 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz","integrity":"sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==","time":1775676222261,"size":37063,"metadata":{"time":1775676222184,"url":"https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"\"f39a196229b27e1ce44921a87b585754\"","last-modified":"Wed, 04 Mar 2026 23:48:49 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
747f77f539ea54d0e097d7094f4bdd6362e40764 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/playwright","integrity":"sha512-JZS2Ayi7OCt9nHPPDCsPIByOFbCJzSGIOlwSfEmyKeQYbqRH8gBQmVYnZYnes31zimglgzGSssxLV9hYKih1BQ==","time":1775676222360,"size":7863829,"metadata":{"time":1775676222074,"url":"https://registry.npmjs.org/playwright","reqHeaders":{"accept":"application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/vnd.npm.install-v1+json","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"W/\"0af545d6bb16402c56505da5c13457e6\"","last-modified":"Wed, 08 Apr 2026 05:46:46 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
657ef7f578699f6ba3e74e4891c958c321eac892 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz","integrity":"sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==","time":1775676221914,"size":2926,"metadata":{"time":1775676221909,"url":"https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"\"63e13db7b1226de0293cc37de64d0cb0\"","last-modified":"Sat, 23 Feb 2019 00:06:55 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
c54b4e0d1757ef5c27eff3c89eda90e3cee7e900 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz","integrity":"sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==","time":1775676222190,"size":10344,"metadata":{"time":1775676222175,"url":"https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"\"105122457074c9735aaa177db219fdaa\"","last-modified":"Wed, 07 Aug 2019 01:15:37 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
a27a07afe6fcbe526e16dff5d85bba417e05be18 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg-native","integrity":"sha512-9cIMZ0dnV3QxY1C2yWm3Vdzg3zyJTDv2U4xNzVeO24xq/lo/GwDjPGIVXxQCBZuSZmD/A63sA0GKEplMa/LIew==","time":1775676221066,"size":90863,"metadata":{"time":1775676221058,"url":"https://registry.npmjs.org/pg-native","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:41 GMT","etag":"W/\"3cdb8987704fc8351f828a02db1e48e7\"","last-modified":"Wed, 04 Mar 2026 23:48:48 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
d1f63828c5fde0c331efed2e2ecddcad6f46f287 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/split2","integrity":"sha512-41J/dMPqHO1Vi2T18ZXocHdWTqFl6hF9G4Vtb/qJL1Niw2KyX+kaB4ryxq8Xg87XSr1rK7aLKd3yT53ppln2nQ==","time":1775676221643,"size":47438,"metadata":{"time":1775676221621,"url":"https://registry.npmjs.org/split2","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:41 GMT","etag":"W/\"516597b215ad557d0d2022aae3b7ef1e\"","last-modified":"Sun, 26 Mar 2023 08:33:04 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
c6a428580823c9e83b92bd6221b03d9c6560e1d0 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz","integrity":"sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==","time":1775676221915,"size":2836,"metadata":{"time":1775676221911,"url":"https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"\"01904b110a17c39d4d627730dff86695\"","last-modified":"Fri, 28 Aug 2020 21:29:06 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
12b5e7e28ae1253bebbed23431c167fbd368bb01 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz","integrity":"sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==","time":1775676222309,"size":5702,"metadata":{"time":1775676222302,"url":"https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"\"d08012f27d64daf94fc156435b607fc9\"","last-modified":"Wed, 04 Mar 2026 23:48:48 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
d13011e10dbaff648846081962b773126047dee3 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/split2/-/split2-4.2.0.tgz","integrity":"sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==","time":1775676221916,"size":4668,"metadata":{"time":1775676221908,"url":"https://registry.npmjs.org/split2/-/split2-4.2.0.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"\"b20ae4aab9e572e34ea9d782ac6e166d\"","last-modified":"Sun, 26 Mar 2023 08:33:04 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
6545508a21f3b11d07e333ce409cc86f3651236f {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg-pool","integrity":"sha512-2iBPR2CWV2T+mxn2j41nm3DljQQ5qfBoHY5T6JSSyY1B09syjb3jHn/j/0yxqFMG25+/LxXCoOouhGfeWjsmvA==","time":1775676221474,"size":130273,"metadata":{"time":1775676221401,"url":"https://registry.npmjs.org/pg-pool","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:41 GMT","etag":"W/\"ca69149740e32bef069805069c7af837\"","last-modified":"Wed, 04 Mar 2026 23:48:48 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
a0c41ba5f661d2d04dd7ed76499d51e95b427cdd {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/postgres-date","integrity":"sha512-tNTzsQEdGKnLXqok4hN9txBHXmaeJEWRcBeCH4A4nYJOcZLqvNPbag31Tb7IrtPhFU1T3jiW2AGE+Ooi7cd+jw==","time":1775676221627,"size":27126,"metadata":{"time":1775676221623,"url":"https://registry.npmjs.org/postgres-date","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:41 GMT","etag":"W/\"4913131aa5d198c99d7eee85d45022ee\"","last-modified":"Fri, 24 Jun 2022 11:20:08 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
abe81ace19b35ed4b7488dac0cee876e7cfb6790 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg/-/pg-8.20.0.tgz","integrity":"sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==","time":1775676222232,"size":25343,"metadata":{"time":1775676222177,"url":"https://registry.npmjs.org/pg/-/pg-8.20.0.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"\"d9a1c81f241a236f4fdf5eae1659721a\"","last-modified":"Wed, 04 Mar 2026 23:48:51 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
4d2ec9a129afabbcda4663ceacc819002b689e8c {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/postgres-array","integrity":"sha512-Jl0xetBCX+EJU/0aNdNnxtx2Ci9hGtVBfwgahd1x9W11mJzr3PIv8lsztw5KQDuhrbVQpk/Y8GewHRu09YdmIQ==","time":1775676221629,"size":21967,"metadata":{"time":1775676221620,"url":"https://registry.npmjs.org/postgres-array","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:41 GMT","etag":"W/\"f2191394df931f08a2f5eff39bb81a5b\"","last-modified":"Thu, 06 Mar 2025 16:20:53 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
a58efd8da8ea83d242ddc7ec66fc2857e413a1eb {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg","integrity":"sha512-r1nBVgXw4XdIq9rhPROxaut6Rd4JuhkeleSfTj727gwbG008AGW0GOphaUJWI7boJ25A0E8h4FSXEudaetjzsQ==","time":1775676220977,"size":475297,"metadata":{"time":1775676220847,"url":"https://registry.npmjs.org/pg","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:40 GMT","etag":"W/\"7330bcc4d6bc209fbabfab6b7f1ab142\"","last-modified":"Wed, 04 Mar 2026 23:48:50 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
46e8ae11211806d7acdf238b6d268442a0d8bea9 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz","integrity":"sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==","time":1775676222193,"size":9572,"metadata":{"time":1775676222179,"url":"https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"\"72b789ba8ed1b9f2f603805a65916dd7\"","last-modified":"Wed, 04 Mar 2026 23:48:48 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
79afed514add49aef0078de47f2171e471b74b7f {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg-int8","integrity":"sha512-PobsyqytLLfmrXKTgwdWDS0ZeB62qFOwA5xA6TFoRQG3rVWZZx6tZHGWoW4e+/QNLGskYl1AT4+cKgtAhN9gwA==","time":1775676221622,"size":3652,"metadata":{"time":1775676221619,"url":"https://registry.npmjs.org/pg-int8","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:41 GMT","etag":"W/\"98260081781de6a352d2a0a3bf91eb08\"","last-modified":"Thu, 12 May 2022 20:28:24 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
166d4945945fbff9d52383789224e14482e98b8d {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/xtend","integrity":"sha512-uxrxUcjlvpSpraPhsG7EO4uLy2sKhxWGlTn3rs/b4uxk/pElMy4QoB0Mt7oGfvUOC4JMFhaZJJBk+tKyRfzrVQ==","time":1775676221811,"size":28314,"metadata":{"time":1775676221809,"url":"https://registry.npmjs.org/xtend","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:41 GMT","etag":"W/\"8bf1b3379bb8a4110f872d1d4929d719\"","last-modified":"Mon, 10 Jul 2023 23:17:32 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
7d26ed53ded1afe6fa02939da65d30e24616e102 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz","integrity":"sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==","time":1775676222192,"size":6852,"metadata":{"time":1775676222176,"url":"https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"\"70b4935d9698c25d0d4e9afe5445cdb0\"","last-modified":"Wed, 14 Jan 2026 18:07:37 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
fae2d42325a026b6e9f2a9d4a5200d8d5e040366 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg-types","integrity":"sha512-4IjVRU+95SG/y7SpODKJIsu+kaK4oQiZVe8LeauY/i50QBvpviSorOzHeN3MmowxmdguN5QRrYxorgC9LiExbA==","time":1775676221522,"size":51397,"metadata":{"time":1775676221501,"url":"https://registry.npmjs.org/pg-types","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:41 GMT","etag":"W/\"a2a94c1d5d207d1543e5516baec38a5f\"","last-modified":"Wed, 30 Jul 2025 15:37:03 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
fac979e5d3746e3af34896a0ac4e01839cf41b0b {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pgpass","integrity":"sha512-DDKg3DBIM2CjCtgP00cstthxBNCyrLOhKaqBKnjjCc2ZWI0fBfH0QHlTtIVbKRYpdabhkzTwsZnmD91x488frg==","time":1775676221393,"size":26739,"metadata":{"time":1775676221383,"url":"https://registry.npmjs.org/pgpass","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:41 GMT","etag":"W/\"1b1bb837ed7dd25808702938dc8a74a7\"","last-modified":"Thu, 23 Jun 2022 20:06:09 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
333081073c70581890963e0d30e6cc4c9752df3a {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz","integrity":"sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==","time":1775676221914,"size":2529,"metadata":{"time":1775676221906,"url":"https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"\"183e6eef1df0529fd39bd932447ba547\"","last-modified":"Mon, 08 Jul 2019 13:35:50 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
11b8b789c260f55d95d1e8b090873671bbfb459d {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/postgres-interval","integrity":"sha512-lQyw10rQNdKdOXtsbFaDhIrM/o6yCnN26jj2CA4jxAQX9B+K/d5RhVoFfKM/TOcht1kscInEGe8L0mDxaDKnqQ==","time":1775676221730,"size":30514,"metadata":{"time":1775676221723,"url":"https://registry.npmjs.org/postgres-interval","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:41 GMT","etag":"W/\"b807a48e3096ef5191a0e3014a51bbec\"","last-modified":"Mon, 29 Jan 2024 22:56:38 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
46bc9ae5bad0563e91df207913ee81905913d4a3 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg-connection-string","integrity":"sha512-MlTwgueP8BGtvCHBmLEL1VIoHASMsH88pY3mbfB/GS9jJNQaiGDhVT6UsyskRMH8K8ab26/6F/cZLRQtEHi58A==","time":1775676221149,"size":74247,"metadata":{"time":1775676221141,"url":"https://registry.npmjs.org/pg-connection-string","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:41 GMT","etag":"W/\"30ca1cf8c86ef430d3624f73637e8223\"","last-modified":"Wed, 04 Mar 2026 23:48:48 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
8ef975e638441d3f878c02418fad64700b104a45 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz","integrity":"sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==","time":1775676222182,"size":4280,"metadata":{"time":1775676222177,"url":"https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"\"e989de90f99a6141e8e2cd57efda6ead\"","last-modified":"Sun, 19 Dec 2021 11:51:55 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
91ec36f1a1bdfbe484b4582e78450ea5c6ea787b {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg-protocol","integrity":"sha512-6FpKGXxJrGhlgNvo5H8JNRFeN2HzhnL1DUsQSnKGb+2GD5zNIu6K4PCpkF0cntXAh7br5mlZmTOezzShKvX0SQ==","time":1775676221416,"size":51532,"metadata":{"time":1775676221396,"url":"https://registry.npmjs.org/pg-protocol","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:41 GMT","etag":"W/\"524a62a456c3c7210a50c43191881599\"","last-modified":"Wed, 04 Mar 2026 23:48:49 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
c2f2e98ead22032f1589203c6e51b6bc558ca9ac {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz","integrity":"sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==","time":1775676221914,"size":2213,"metadata":{"time":1775676221903,"url":"https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"\"fdf7b39289eab68d3e6d6df5ab9dbb80\"","last-modified":"Mon, 22 Oct 2018 18:12:17 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
9909c82e1c0af0063aa4e39025d1371a3e84412b {"key":"security-advisory:playwright:a407zOIYdDh947DlyphC5oEdapkpLBJxOgChbl8bz0qgm09nkiNQPvR//cOdnaUrI7KtQLhhSOd7xhRGTDkyRQ==","integrity":"sha512-Aj3b1mc597imTrdExp3dawx4pJLtCjp/CO7hxYZPSTjVAUGlT2URLnGBMBtqIPRY5wQbghfNUlr/pGZNp/H5pw==","time":1775676222525,"size":281462}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
1d8d9f88aeb0b55880d30a75a41bfba6cc49b5f3 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg-cloudflare","integrity":"sha512-9gAH8cVV/wM7rqZvj84BSmwyKwzgFLyATN9tlVHjzVEzv2/E1co4WT3MbTSxiShxJjn9AxzqH0j0OdDH3reZ6w==","time":1775676221391,"size":20882,"metadata":{"time":1775676221382,"url":"https://registry.npmjs.org/pg-cloudflare","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Wed, 08 Apr 2026 19:23:41 GMT","etag":"W/\"fdd2a2a1dfa9ab1971426efa29685477\"","last-modified":"Wed, 14 Jan 2026 18:07:37 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
f1958765790aecbbad764e0e7deb8ce3ecd5322c {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz","integrity":"sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==","time":1775676222181,"size":1508,"metadata":{"time":1775676222172,"url":"https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"\"21eccd4585fc823b79777973c4abf089\"","last-modified":"Sun, 27 May 2018 12:09:34 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
93b88a431cf22631d60cfed0878ae3993066f003 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz","integrity":"sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==","time":1775676222159,"size":1724,"metadata":{"time":1775676222155,"url":"https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Wed, 08 Apr 2026 19:23:42 GMT","etag":"\"643609af3f34ea824bd73d244f271e2f\"","last-modified":"Wed, 17 Dec 2025 04:53:25 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
0 verbose cli /opt/homebrew/Cellar/node@24/24.12.0/bin/node /opt/homebrew/opt/node@24/bin/npm
|
||||||
|
1 info using npm@11.6.2
|
||||||
|
2 info using node@v24.12.0
|
||||||
|
3 silly config load:file:/opt/homebrew/Cellar/node@24/24.12.0/lib/node_modules/npm/npmrc
|
||||||
|
4 silly config load:file:/Users/yml/codes/order-site-workspace/apps/backend/.npmrc
|
||||||
|
5 silly config load:file:/Users/yml/.npmrc
|
||||||
|
6 silly config load:file:/opt/homebrew/etc/npmrc
|
||||||
|
7 verbose title npm install
|
||||||
|
8 verbose argv "install"
|
||||||
|
9 verbose logfile logs-max:10 dir:/Users/yml/codes/order-site-workspace/apps/backend/.npm-cache/_logs/2026-04-08T19_23_39_509Z-
|
||||||
|
10 verbose logfile /Users/yml/codes/order-site-workspace/apps/backend/.npm-cache/_logs/2026-04-08T19_23_39_509Z-debug-0.log
|
||||||
|
11 silly logfile done cleaning log files
|
||||||
|
12 silly packumentCache heap:4496293888 maxSize:1124073472 maxEntrySize:562036736
|
||||||
|
13 silly idealTree buildDeps
|
||||||
|
14 silly fetch manifest pg@^8.16.3
|
||||||
|
15 silly packumentCache full:https://registry.npmjs.org/pg cache-miss
|
||||||
|
16 http fetch GET 200 https://registry.npmjs.org/pg 1148ms (cache miss)
|
||||||
|
17 silly packumentCache full:https://registry.npmjs.org/pg set size:undefined disposed:false
|
||||||
|
18 silly fetch manifest pg-native@>=3.0.1
|
||||||
|
19 silly packumentCache full:https://registry.npmjs.org/pg-native cache-miss
|
||||||
|
20 http fetch GET 200 https://registry.npmjs.org/pg-native 84ms (cache miss)
|
||||||
|
21 silly packumentCache full:https://registry.npmjs.org/pg-native set size:undefined disposed:false
|
||||||
|
22 silly placeDep ROOT pg@8.20.0 OK for: order-site-backend@0.1.0 want: ^8.16.3
|
||||||
|
23 silly fetch manifest pg-connection-string@^2.12.0
|
||||||
|
24 silly packumentCache full:https://registry.npmjs.org/pg-connection-string cache-miss
|
||||||
|
25 silly fetch manifest pg-pool@^3.13.0
|
||||||
|
26 silly packumentCache full:https://registry.npmjs.org/pg-pool cache-miss
|
||||||
|
27 silly fetch manifest pg-protocol@^1.13.0
|
||||||
|
28 silly packumentCache full:https://registry.npmjs.org/pg-protocol cache-miss
|
||||||
|
29 silly fetch manifest pg-types@2.2.0
|
||||||
|
30 silly packumentCache full:https://registry.npmjs.org/pg-types cache-miss
|
||||||
|
31 silly fetch manifest pgpass@1.0.5
|
||||||
|
32 silly packumentCache full:https://registry.npmjs.org/pgpass cache-miss
|
||||||
|
33 silly fetch manifest pg-cloudflare@^1.3.0
|
||||||
|
34 silly packumentCache full:https://registry.npmjs.org/pg-cloudflare cache-miss
|
||||||
|
35 http fetch GET 200 https://registry.npmjs.org/pg-connection-string 82ms (cache miss)
|
||||||
|
36 silly packumentCache full:https://registry.npmjs.org/pg-connection-string set size:undefined disposed:false
|
||||||
|
37 http fetch GET 200 https://registry.npmjs.org/pg-cloudflare 323ms (cache miss)
|
||||||
|
38 silly packumentCache full:https://registry.npmjs.org/pg-cloudflare set size:undefined disposed:false
|
||||||
|
39 http fetch GET 200 https://registry.npmjs.org/pgpass 325ms (cache miss)
|
||||||
|
40 silly packumentCache full:https://registry.npmjs.org/pgpass set size:undefined disposed:false
|
||||||
|
41 http fetch GET 200 https://registry.npmjs.org/pg-protocol 348ms (cache miss)
|
||||||
|
42 silly packumentCache full:https://registry.npmjs.org/pg-protocol set size:undefined disposed:false
|
||||||
|
43 http fetch GET 200 https://registry.npmjs.org/pg-pool 406ms (cache miss)
|
||||||
|
44 silly packumentCache full:https://registry.npmjs.org/pg-pool set size:undefined disposed:false
|
||||||
|
45 http fetch GET 200 https://registry.npmjs.org/pg-types 454ms (cache miss)
|
||||||
|
46 silly packumentCache full:https://registry.npmjs.org/pg-types set size:undefined disposed:false
|
||||||
|
47 silly fetch manifest pg@>=8.0
|
||||||
|
48 silly packumentCache full:https://registry.npmjs.org/pg cache-miss
|
||||||
|
49 http cache https://registry.npmjs.org/pg 7ms (cache hit)
|
||||||
|
50 silly packumentCache full:https://registry.npmjs.org/pg set size:475297 disposed:false
|
||||||
|
51 silly placeDep ROOT pg-cloudflare@1.3.0 OK for: pg@8.20.0 want: ^1.3.0
|
||||||
|
52 silly placeDep ROOT pg-connection-string@2.12.0 OK for: pg@8.20.0 want: ^2.12.0
|
||||||
|
53 silly placeDep ROOT pg-pool@3.13.0 OK for: pg@8.20.0 want: ^3.13.0
|
||||||
|
54 silly placeDep ROOT pg-protocol@1.13.0 OK for: pg@8.20.0 want: ^1.13.0
|
||||||
|
55 silly placeDep ROOT pg-types@2.2.0 OK for: pg@8.20.0 want: 2.2.0
|
||||||
|
56 silly placeDep ROOT pgpass@1.0.5 OK for: pg@8.20.0 want: 1.0.5
|
||||||
|
57 silly fetch manifest pg-int8@1.0.1
|
||||||
|
58 silly packumentCache full:https://registry.npmjs.org/pg-int8 cache-miss
|
||||||
|
59 silly fetch manifest postgres-date@~1.0.4
|
||||||
|
60 silly packumentCache full:https://registry.npmjs.org/postgres-date cache-miss
|
||||||
|
61 silly fetch manifest postgres-array@~2.0.0
|
||||||
|
62 silly packumentCache full:https://registry.npmjs.org/postgres-array cache-miss
|
||||||
|
63 silly fetch manifest postgres-bytea@~1.0.0
|
||||||
|
64 silly packumentCache full:https://registry.npmjs.org/postgres-bytea cache-miss
|
||||||
|
65 silly fetch manifest postgres-interval@^1.1.0
|
||||||
|
66 silly packumentCache full:https://registry.npmjs.org/postgres-interval cache-miss
|
||||||
|
67 silly fetch manifest split2@^4.1.0
|
||||||
|
68 silly packumentCache full:https://registry.npmjs.org/split2 cache-miss
|
||||||
|
69 http fetch GET 200 https://registry.npmjs.org/pg-int8 85ms (cache miss)
|
||||||
|
70 silly packumentCache full:https://registry.npmjs.org/pg-int8 set size:undefined disposed:false
|
||||||
|
71 http fetch GET 200 https://registry.npmjs.org/postgres-bytea 86ms (cache miss)
|
||||||
|
72 silly packumentCache full:https://registry.npmjs.org/postgres-bytea set size:undefined disposed:false
|
||||||
|
73 http fetch GET 200 https://registry.npmjs.org/postgres-date 88ms (cache miss)
|
||||||
|
74 silly packumentCache full:https://registry.npmjs.org/postgres-date set size:undefined disposed:false
|
||||||
|
75 http fetch GET 200 https://registry.npmjs.org/postgres-array 89ms (cache miss)
|
||||||
|
76 silly packumentCache full:https://registry.npmjs.org/postgres-array set size:undefined disposed:false
|
||||||
|
77 http fetch GET 200 https://registry.npmjs.org/split2 102ms (cache miss)
|
||||||
|
78 silly packumentCache full:https://registry.npmjs.org/split2 set size:undefined disposed:false
|
||||||
|
79 http fetch GET 200 https://registry.npmjs.org/postgres-interval 189ms (cache miss)
|
||||||
|
80 silly packumentCache full:https://registry.npmjs.org/postgres-interval set size:undefined disposed:false
|
||||||
|
81 silly placeDep ROOT pg-int8@1.0.1 OK for: pg-types@2.2.0 want: 1.0.1
|
||||||
|
82 silly placeDep ROOT postgres-array@2.0.0 OK for: pg-types@2.2.0 want: ~2.0.0
|
||||||
|
83 silly placeDep ROOT postgres-bytea@1.0.1 OK for: pg-types@2.2.0 want: ~1.0.0
|
||||||
|
84 silly placeDep ROOT postgres-date@1.0.7 OK for: pg-types@2.2.0 want: ~1.0.4
|
||||||
|
85 silly placeDep ROOT postgres-interval@1.2.0 OK for: pg-types@2.2.0 want: ^1.1.0
|
||||||
|
86 silly fetch manifest xtend@^4.0.0
|
||||||
|
87 silly packumentCache full:https://registry.npmjs.org/xtend cache-miss
|
||||||
|
88 http fetch GET 200 https://registry.npmjs.org/xtend 77ms (cache miss)
|
||||||
|
89 silly packumentCache full:https://registry.npmjs.org/xtend set size:undefined disposed:false
|
||||||
|
90 silly placeDep ROOT split2@4.2.0 OK for: pgpass@1.0.5 want: ^4.1.0
|
||||||
|
91 silly placeDep ROOT xtend@4.0.2 OK for: postgres-interval@1.2.0 want: ^4.0.0
|
||||||
|
92 silly reify moves {}
|
||||||
|
93 silly audit bulk request {
|
||||||
|
93 silly audit accepts: [ '2.0.0' ],
|
||||||
|
93 silly audit 'body-parser': [ '2.2.2' ],
|
||||||
|
93 silly audit bytes: [ '3.1.2' ],
|
||||||
|
93 silly audit 'call-bind-apply-helpers': [ '1.0.2' ],
|
||||||
|
93 silly audit 'call-bound': [ '1.0.4' ],
|
||||||
|
93 silly audit 'content-disposition': [ '1.0.1' ],
|
||||||
|
93 silly audit 'content-type': [ '1.0.5' ],
|
||||||
|
93 silly audit cookie: [ '0.7.2' ],
|
||||||
|
93 silly audit 'cookie-signature': [ '1.2.2' ],
|
||||||
|
93 silly audit debug: [ '4.4.3' ],
|
||||||
|
93 silly audit depd: [ '2.0.0' ],
|
||||||
|
93 silly audit 'dunder-proto': [ '1.0.1' ],
|
||||||
|
93 silly audit 'ee-first': [ '1.1.1' ],
|
||||||
|
93 silly audit encodeurl: [ '2.0.0' ],
|
||||||
|
93 silly audit 'es-define-property': [ '1.0.1' ],
|
||||||
|
93 silly audit 'es-errors': [ '1.3.0' ],
|
||||||
|
93 silly audit 'es-object-atoms': [ '1.1.1' ],
|
||||||
|
93 silly audit 'escape-html': [ '1.0.3' ],
|
||||||
|
93 silly audit etag: [ '1.8.1' ],
|
||||||
|
93 silly audit express: [ '5.2.1' ],
|
||||||
|
93 silly audit finalhandler: [ '2.1.1' ],
|
||||||
|
93 silly audit forwarded: [ '0.2.0' ],
|
||||||
|
93 silly audit fresh: [ '2.0.0' ],
|
||||||
|
93 silly audit fsevents: [ '2.3.2' ],
|
||||||
|
93 silly audit 'function-bind': [ '1.1.2' ],
|
||||||
|
93 silly audit 'get-intrinsic': [ '1.3.0' ],
|
||||||
|
93 silly audit 'get-proto': [ '1.0.1' ],
|
||||||
|
93 silly audit gopd: [ '1.2.0' ],
|
||||||
|
93 silly audit 'has-symbols': [ '1.1.0' ],
|
||||||
|
93 silly audit hasown: [ '2.0.2' ],
|
||||||
|
93 silly audit 'http-errors': [ '2.0.1' ],
|
||||||
|
93 silly audit 'iconv-lite': [ '0.7.2' ],
|
||||||
|
93 silly audit inherits: [ '2.0.4' ],
|
||||||
|
93 silly audit 'ipaddr.js': [ '1.9.1' ],
|
||||||
|
93 silly audit 'is-promise': [ '4.0.0' ],
|
||||||
|
93 silly audit 'math-intrinsics': [ '1.1.0' ],
|
||||||
|
93 silly audit 'media-typer': [ '1.1.0' ],
|
||||||
|
93 silly audit 'merge-descriptors': [ '2.0.0' ],
|
||||||
|
93 silly audit 'mime-db': [ '1.54.0' ],
|
||||||
|
93 silly audit 'mime-types': [ '3.0.2' ],
|
||||||
|
93 silly audit ms: [ '2.1.3' ],
|
||||||
|
93 silly audit negotiator: [ '1.0.0' ],
|
||||||
|
93 silly audit 'object-inspect': [ '1.13.4' ],
|
||||||
|
93 silly audit 'on-finished': [ '2.4.1' ],
|
||||||
|
93 silly audit once: [ '1.4.0' ],
|
||||||
|
93 silly audit parseurl: [ '1.3.3' ],
|
||||||
|
93 silly audit 'path-to-regexp': [ '8.4.2' ],
|
||||||
|
93 silly audit playwright: [ '1.42.1' ],
|
||||||
|
93 silly audit 'playwright-core': [ '1.42.1' ],
|
||||||
|
93 silly audit 'proxy-addr': [ '2.0.7' ],
|
||||||
|
93 silly audit qs: [ '6.15.0' ],
|
||||||
|
93 silly audit 'range-parser': [ '1.2.1' ],
|
||||||
|
93 silly audit 'raw-body': [ '3.0.2' ],
|
||||||
|
93 silly audit router: [ '2.2.0' ],
|
||||||
|
93 silly audit 'safer-buffer': [ '2.1.2' ],
|
||||||
|
93 silly audit send: [ '1.2.1' ],
|
||||||
|
93 silly audit 'serve-static': [ '2.2.1' ],
|
||||||
|
93 silly audit setprototypeof: [ '1.2.0' ],
|
||||||
|
93 silly audit 'side-channel': [ '1.1.0' ],
|
||||||
|
93 silly audit 'side-channel-list': [ '1.0.0' ],
|
||||||
|
93 silly audit 'side-channel-map': [ '1.0.1' ],
|
||||||
|
93 silly audit 'side-channel-weakmap': [ '1.0.2' ],
|
||||||
|
93 silly audit statuses: [ '2.0.2' ],
|
||||||
|
93 silly audit toidentifier: [ '1.0.1' ],
|
||||||
|
93 silly audit 'type-is': [ '2.0.1' ],
|
||||||
|
93 silly audit unpipe: [ '1.0.0' ],
|
||||||
|
93 silly audit vary: [ '1.1.2' ],
|
||||||
|
93 silly audit wrappy: [ '1.0.2' ],
|
||||||
|
93 silly audit pg: [ '8.20.0' ],
|
||||||
|
93 silly audit 'pg-cloudflare': [ '1.3.0' ],
|
||||||
|
93 silly audit 'pg-connection-string': [ '2.12.0' ],
|
||||||
|
93 silly audit 'pg-pool': [ '3.13.0' ],
|
||||||
|
93 silly audit 'pg-protocol': [ '1.13.0' ],
|
||||||
|
93 silly audit 'pg-types': [ '2.2.0' ],
|
||||||
|
93 silly audit pgpass: [ '1.0.5' ],
|
||||||
|
93 silly audit 'pg-int8': [ '1.0.1' ],
|
||||||
|
93 silly audit 'postgres-array': [ '2.0.0' ],
|
||||||
|
93 silly audit 'postgres-bytea': [ '1.0.1' ],
|
||||||
|
93 silly audit 'postgres-date': [ '1.0.7' ],
|
||||||
|
93 silly audit 'postgres-interval': [ '1.2.0' ],
|
||||||
|
93 silly audit split2: [ '4.2.0' ],
|
||||||
|
93 silly audit xtend: [ '4.0.2' ]
|
||||||
|
93 silly audit }
|
||||||
|
94 http cache xtend@https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz 0ms (cache hit)
|
||||||
|
95 http cache postgres-interval@https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz 0ms (cache hit)
|
||||||
|
96 http cache split2@https://registry.npmjs.org/split2/-/split2-4.2.0.tgz 1ms (cache hit)
|
||||||
|
97 http cache postgres-date@https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz 0ms (cache hit)
|
||||||
|
98 http cache postgres-array@https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz 0ms (cache hit)
|
||||||
|
99 http cache postgres-bytea@https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz 0ms (cache hit)
|
||||||
|
100 http cache pgpass@https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz 0ms (cache hit)
|
||||||
|
101 http cache pg-int8@https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz 0ms (cache hit)
|
||||||
|
102 http cache pg-protocol@https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz 0ms (cache hit)
|
||||||
|
103 http cache pg-types@https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz 0ms (cache hit)
|
||||||
|
104 http cache pg-pool@https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz 0ms (cache hit)
|
||||||
|
105 http cache pg-connection-string@https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz 0ms (cache hit)
|
||||||
|
106 http cache pg-cloudflare@https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz 0ms (cache hit)
|
||||||
|
107 http cache pg@https://registry.npmjs.org/pg/-/pg-8.20.0.tgz 0ms (cache hit)
|
||||||
|
108 silly tarball no local data for xtend@https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz. Extracting by manifest.
|
||||||
|
109 silly tarball no local data for postgres-interval@https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz. Extracting by manifest.
|
||||||
|
110 silly tarball no local data for split2@https://registry.npmjs.org/split2/-/split2-4.2.0.tgz. Extracting by manifest.
|
||||||
|
111 silly tarball no local data for postgres-date@https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz. Extracting by manifest.
|
||||||
|
112 silly tarball no local data for postgres-array@https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz. Extracting by manifest.
|
||||||
|
113 silly tarball no local data for postgres-bytea@https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz. Extracting by manifest.
|
||||||
|
114 silly tarball no local data for pgpass@https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz. Extracting by manifest.
|
||||||
|
115 silly tarball no local data for pg-int8@https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz. Extracting by manifest.
|
||||||
|
116 silly tarball no local data for pg-protocol@https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz. Extracting by manifest.
|
||||||
|
117 silly tarball no local data for pg-types@https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz. Extracting by manifest.
|
||||||
|
118 silly tarball no local data for pg-pool@https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz. Extracting by manifest.
|
||||||
|
119 silly tarball no local data for pg-connection-string@https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz. Extracting by manifest.
|
||||||
|
120 silly tarball no local data for pg-cloudflare@https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz. Extracting by manifest.
|
||||||
|
121 silly tarball no local data for pg@https://registry.npmjs.org/pg/-/pg-8.20.0.tgz. Extracting by manifest.
|
||||||
|
122 http fetch GET 200 https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz 87ms (cache miss)
|
||||||
|
123 http fetch GET 200 https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz 89ms (cache miss)
|
||||||
|
124 http fetch GET 200 https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz 88ms (cache miss)
|
||||||
|
125 http fetch GET 200 https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz 89ms (cache miss)
|
||||||
|
126 http fetch GET 200 https://registry.npmjs.org/split2/-/split2-4.2.0.tgz 89ms (cache miss)
|
||||||
|
127 http fetch POST 200 https://registry.npmjs.org/-/npm/v1/security/advisories/bulk 176ms
|
||||||
|
128 silly audit report {
|
||||||
|
128 silly audit report playwright: [
|
||||||
|
128 silly audit report {
|
||||||
|
128 silly audit report id: 1109208,
|
||||||
|
128 silly audit report url: 'https://github.com/advisories/GHSA-7mvr-c777-76hp',
|
||||||
|
128 silly audit report title: 'Playwright downloads and installs browsers without verifying the authenticity of the SSL certificate',
|
||||||
|
128 silly audit report severity: 'high',
|
||||||
|
128 silly audit report vulnerable_versions: '<1.55.1',
|
||||||
|
128 silly audit report cwe: [Array],
|
||||||
|
128 silly audit report cvss: [Object]
|
||||||
|
128 silly audit report }
|
||||||
|
128 silly audit report ]
|
||||||
|
128 silly audit report }
|
||||||
|
129 silly packumentCache corgi:https://registry.npmjs.org/playwright cache-miss
|
||||||
|
130 http fetch GET 200 https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz 332ms (cache miss)
|
||||||
|
131 http fetch GET 200 https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz 354ms (cache miss)
|
||||||
|
132 http fetch GET 200 https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz 357ms (cache miss)
|
||||||
|
133 http fetch GET 200 https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz 363ms (cache miss)
|
||||||
|
134 http fetch GET 200 https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz 364ms (cache miss)
|
||||||
|
135 http fetch GET 200 https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz 365ms (cache miss)
|
||||||
|
136 http fetch GET 200 https://registry.npmjs.org/pg/-/pg-8.20.0.tgz 402ms (cache miss)
|
||||||
|
137 http fetch GET 200 https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz 432ms (cache miss)
|
||||||
|
138 http fetch GET 200 https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz 481ms (cache miss)
|
||||||
|
139 http fetch GET 200 https://registry.npmjs.org/playwright 363ms (cache miss)
|
||||||
|
140 silly packumentCache corgi:https://registry.npmjs.org/playwright set size:undefined disposed:false
|
||||||
|
141 silly ADD node_modules/xtend
|
||||||
|
142 silly ADD node_modules/split2
|
||||||
|
143 silly ADD node_modules/postgres-interval
|
||||||
|
144 silly ADD node_modules/postgres-date
|
||||||
|
145 silly ADD node_modules/postgres-bytea
|
||||||
|
146 silly ADD node_modules/postgres-array
|
||||||
|
147 silly ADD node_modules/pg-int8
|
||||||
|
148 silly ADD node_modules/pgpass
|
||||||
|
149 silly ADD node_modules/pg-types
|
||||||
|
150 silly ADD node_modules/pg-protocol
|
||||||
|
151 silly ADD node_modules/pg-pool
|
||||||
|
152 silly ADD node_modules/pg-connection-string
|
||||||
|
153 silly ADD node_modules/pg-cloudflare
|
||||||
|
154 silly ADD node_modules/pg
|
||||||
|
155 verbose cwd /Users/yml/codes/order-site-workspace/apps/backend
|
||||||
|
156 verbose os Darwin 25.4.0
|
||||||
|
157 verbose node v24.12.0
|
||||||
|
158 verbose npm v11.6.2
|
||||||
|
159 verbose exit 0
|
||||||
|
160 info ok
|
||||||
@@ -2,126 +2,72 @@ const path = require('node:path')
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
server: {
|
server: {
|
||||||
// 后端监听端口。
|
|
||||||
port: 3000,
|
port: 3000,
|
||||||
},
|
},
|
||||||
|
|
||||||
browser: {
|
browser: {
|
||||||
// 自定义浏览器可执行文件路径。
|
|
||||||
// 留空时默认使用 Playwright 自带的 Chromium。
|
|
||||||
chromePath: '',
|
chromePath: '',
|
||||||
|
|
||||||
// 是否启用无头模式。
|
|
||||||
// true / false 表示强制指定,null 表示按环境自动判断。
|
|
||||||
headless: null,
|
headless: null,
|
||||||
|
|
||||||
// 是否自动打开 DevTools。
|
|
||||||
// true / false 表示强制指定,null 表示按环境自动判断。
|
|
||||||
devtools: null,
|
devtools: null,
|
||||||
|
|
||||||
// 是否让浏览器在空闲时继续常驻后台。
|
|
||||||
// true / false 表示强制指定,null 表示按环境自动判断。
|
|
||||||
keepAlive: null,
|
keepAlive: null,
|
||||||
|
|
||||||
// 服务启动后是否立即预热浏览器,减少第一次创建会话的冷启动。
|
|
||||||
// true / false 表示强制指定,null 表示按环境自动判断。
|
|
||||||
prewarm: null,
|
prewarm: null,
|
||||||
|
|
||||||
// Playwright slowMo,单位毫秒。
|
|
||||||
// 设为 null 时,开发环境默认 150,生产环境默认 0。
|
|
||||||
slowMoMs: null,
|
slowMoMs: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
session: {
|
session: {
|
||||||
// 是否输出浏览器会话调试日志,并在接口里带出调试字段。
|
|
||||||
debug: false,
|
debug: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
ocr: {
|
ocr: {
|
||||||
// 本地 OCR 子服务目录。
|
|
||||||
projectRoot: path.resolve(__dirname, '../subservices/ocr-worker'),
|
projectRoot: path.resolve(__dirname, '../subservices/ocr-worker'),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
data: {
|
||||||
|
root: path.resolve(__dirname, '../data'),
|
||||||
|
},
|
||||||
|
|
||||||
database: {
|
database: {
|
||||||
// SQLite 数据文件。
|
url: 'postgres://postgres:postgres@127.0.0.1:5432/order_site',
|
||||||
filePath: path.resolve(__dirname, '../data/order-site.db'),
|
ssl: false,
|
||||||
|
maxConnections: 10,
|
||||||
},
|
},
|
||||||
|
|
||||||
orders: {
|
orders: {
|
||||||
// 用户领取页基础链接,不带末尾 token。
|
|
||||||
claimBaseUrl: 'http://127.0.0.1:5173/#/claim',
|
claimBaseUrl: 'http://127.0.0.1:5173/#/claim',
|
||||||
|
|
||||||
// 领取 token 默认有效时长。
|
|
||||||
tokenTtlHours: 24,
|
tokenTtlHours: 24,
|
||||||
|
|
||||||
// 平台商品标识到内部 SKU 的映射。
|
|
||||||
skuMappings: {},
|
skuMappings: {},
|
||||||
|
fulfillmentBindings: [],
|
||||||
},
|
},
|
||||||
|
|
||||||
admin: {
|
admin: {
|
||||||
// 后台 session 签名密钥,留空表示禁用后台登录。
|
|
||||||
sessionSecret: '',
|
sessionSecret: '',
|
||||||
|
|
||||||
// 后台登录态默认有效时长。
|
|
||||||
sessionTtlHours: 12,
|
sessionTtlHours: 12,
|
||||||
|
|
||||||
// 默认初始化账号。仅在数据库里不存在同名用户时创建。
|
|
||||||
defaultUsers: [],
|
defaultUsers: [],
|
||||||
},
|
},
|
||||||
|
|
||||||
platforms: {
|
platforms: {
|
||||||
agiso: {
|
agiso: {
|
||||||
// Agiso 开放平台应用密钥,用于 webhook 验签。
|
|
||||||
appSecret: '',
|
appSecret: '',
|
||||||
|
|
||||||
tradeDetail: {
|
tradeDetail: {
|
||||||
// 咸鱼 webhook 不带金额时,用这个接口按订单号补查订单详情。
|
|
||||||
endpoint: 'https://gw-api.agiso.com/aldsIdle/Order/Detail',
|
endpoint: 'https://gw-api.agiso.com/aldsIdle/Order/Detail',
|
||||||
|
|
||||||
// Header 中的 ApiVersion。
|
|
||||||
apiVersion: '1',
|
apiVersion: '1',
|
||||||
|
|
||||||
// 请求超时,单位毫秒。
|
|
||||||
timeoutMs: 5000,
|
timeoutMs: 5000,
|
||||||
},
|
},
|
||||||
|
|
||||||
messaging: {
|
messaging: {
|
||||||
// 是否启用 Agiso 站内消息发送。
|
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|
||||||
// 发送消息接口。
|
|
||||||
sendMessageEndpoint: 'https://gw-api.agiso.com/aldsIdle/ImMsg/SendMsg',
|
sendMessageEndpoint: 'https://gw-api.agiso.com/aldsIdle/ImMsg/SendMsg',
|
||||||
|
|
||||||
// Header 中的 ApiVersion。
|
|
||||||
apiVersion: '1',
|
apiVersion: '1',
|
||||||
|
|
||||||
// 兼容保留字段,当前发送消息固定使用 Bearer 鉴权。
|
|
||||||
authMode: 'bearer',
|
authMode: 'bearer',
|
||||||
|
|
||||||
// 开放平台应用标识。
|
|
||||||
appId: '',
|
appId: '',
|
||||||
|
|
||||||
// 开放平台 AccessToken。
|
|
||||||
accessToken: '',
|
accessToken: '',
|
||||||
|
|
||||||
// 若单独配置则优先于 platforms.agiso.appSecret。
|
|
||||||
appSecret: '',
|
appSecret: '',
|
||||||
|
|
||||||
// 发送给用户的默认消息模板。
|
|
||||||
messageTemplate: '您的订单 {platformOrderId} 已创建领取链接,请在 {expiredAt} 前完成领取:{claimUrl}',
|
messageTemplate: '您的订单 {platformOrderId} 已创建领取链接,请在 {expiredAt} 前完成领取:{claimUrl}',
|
||||||
|
|
||||||
// 平台内多店铺配置,按 shopId 做覆盖。
|
|
||||||
// 常见场景:同一个 Agiso 应用下,不同店铺使用不同 AccessToken。
|
|
||||||
shops: {},
|
shops: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
redeem: {
|
redeem: {
|
||||||
// 兑换后的证明产物模式:
|
|
||||||
// full = 完整证明(最终图 + 北京时间 + HTML + result.json)
|
|
||||||
// basic = 只保留最终截图和 result.json
|
|
||||||
// off = 完全不生成证明文件
|
|
||||||
proofMode: 'full',
|
proofMode: 'full',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+148
@@ -9,6 +9,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
"pg": "^8.16.3",
|
||||||
"playwright": "1.42.1"
|
"playwright": "1.42.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -584,6 +585,96 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pg": {
|
||||||
|
"version": "8.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
|
||||||
|
"integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"pg-connection-string": "^2.12.0",
|
||||||
|
"pg-pool": "^3.13.0",
|
||||||
|
"pg-protocol": "^1.13.0",
|
||||||
|
"pg-types": "2.2.0",
|
||||||
|
"pgpass": "1.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"pg-cloudflare": "^1.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg-native": ">=3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"pg-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-cloudflare": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/pg-connection-string": {
|
||||||
|
"version": "2.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz",
|
||||||
|
"integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/pg-int8": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-pool": {
|
||||||
|
"version": "3.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz",
|
||||||
|
"integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-protocol": {
|
||||||
|
"version": "1.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz",
|
||||||
|
"integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/pg-types": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"postgres-array": "~2.0.0",
|
||||||
|
"postgres-bytea": "~1.0.0",
|
||||||
|
"postgres-date": "~1.0.4",
|
||||||
|
"postgres-interval": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pgpass": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"split2": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/playwright": {
|
"node_modules/playwright": {
|
||||||
"version": "1.42.1",
|
"version": "1.42.1",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz",
|
||||||
@@ -614,6 +705,45 @@
|
|||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postgres-array": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-bytea": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-date": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-interval": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/proxy-addr": {
|
"node_modules/proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
@@ -811,6 +941,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/split2": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||||
@@ -866,6 +1005,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/xtend": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
"pg": "^8.16.3",
|
||||||
"playwright": "1.42.1"
|
"playwright": "1.42.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,8 +59,7 @@ function loadEnvFile(filePath) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = parseEnvValue(line.slice(separatorIndex + 1))
|
process.env[key] = parseEnvValue(line.slice(separatorIndex + 1))
|
||||||
process.env[key] = value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,14 +126,24 @@ function applyEnvOverrides(baseConfig) {
|
|||||||
nextConfig.ocr.projectRoot = ocrProjectRoot
|
nextConfig.ocr.projectRoot = ocrProjectRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
const proofMode = String(process.env.TENCENT_REDEEM_PROOF_MODE || '').trim()
|
const dataRoot = String(process.env.DATA_ROOT || '').trim()
|
||||||
if (proofMode) {
|
if (dataRoot) {
|
||||||
nextConfig.redeem.proofMode = proofMode
|
nextConfig.data.root = path.resolve(dataRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
const databaseFilePath = String(process.env.DATABASE_FILE_PATH || '').trim()
|
const databaseUrl = String(process.env.DATABASE_URL || '').trim()
|
||||||
if (databaseFilePath) {
|
if (databaseUrl) {
|
||||||
nextConfig.database.filePath = databaseFilePath
|
nextConfig.database.url = databaseUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
const databaseSsl = parseBoolean(process.env.DATABASE_SSL)
|
||||||
|
if (databaseSsl !== null) {
|
||||||
|
nextConfig.database.ssl = databaseSsl
|
||||||
|
}
|
||||||
|
|
||||||
|
const databaseMaxConnections = parseInteger(process.env.DATABASE_MAX_CONNECTIONS)
|
||||||
|
if (databaseMaxConnections !== null) {
|
||||||
|
nextConfig.database.maxConnections = databaseMaxConnections
|
||||||
}
|
}
|
||||||
|
|
||||||
const claimBaseUrl = String(process.env.CLAIM_BASE_URL || '').trim()
|
const claimBaseUrl = String(process.env.CLAIM_BASE_URL || '').trim()
|
||||||
@@ -142,11 +151,21 @@ function applyEnvOverrides(baseConfig) {
|
|||||||
nextConfig.orders.claimBaseUrl = claimBaseUrl
|
nextConfig.orders.claimBaseUrl = claimBaseUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tokenTtlHours = parseInteger(process.env.CLAIM_TOKEN_TTL_HOURS)
|
||||||
|
if (tokenTtlHours !== null) {
|
||||||
|
nextConfig.orders.tokenTtlHours = tokenTtlHours
|
||||||
|
}
|
||||||
|
|
||||||
const skuMappings = parseJsonObject(process.env.ORDER_SKU_MAPPINGS_JSON)
|
const skuMappings = parseJsonObject(process.env.ORDER_SKU_MAPPINGS_JSON)
|
||||||
if (skuMappings) {
|
if (skuMappings) {
|
||||||
nextConfig.orders.skuMappings = skuMappings
|
nextConfig.orders.skuMappings = skuMappings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fulfillmentBindings = parseJsonArray(process.env.ORDER_FULFILLMENT_BINDINGS_JSON)
|
||||||
|
if (fulfillmentBindings) {
|
||||||
|
nextConfig.orders.fulfillmentBindings = fulfillmentBindings
|
||||||
|
}
|
||||||
|
|
||||||
const adminSessionSecret = String(process.env.ADMIN_SESSION_SECRET || '').trim()
|
const adminSessionSecret = String(process.env.ADMIN_SESSION_SECRET || '').trim()
|
||||||
if (adminSessionSecret) {
|
if (adminSessionSecret) {
|
||||||
nextConfig.admin.sessionSecret = adminSessionSecret
|
nextConfig.admin.sessionSecret = adminSessionSecret
|
||||||
@@ -222,6 +241,11 @@ function applyEnvOverrides(baseConfig) {
|
|||||||
nextConfig.platforms.agiso.messaging.shops = normalizeAgisoMessagingShops(agisoShops)
|
nextConfig.platforms.agiso.messaging.shops = normalizeAgisoMessagingShops(agisoShops)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const proofMode = String(process.env.TENCENT_REDEEM_PROOF_MODE || '').trim()
|
||||||
|
if (proofMode) {
|
||||||
|
nextConfig.redeem.proofMode = proofMode
|
||||||
|
}
|
||||||
|
|
||||||
return nextConfig
|
return nextConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,9 +293,7 @@ function isPlainObject(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseBoolean(rawValue) {
|
function parseBoolean(rawValue) {
|
||||||
const normalized = String(rawValue || '')
|
const normalized = String(rawValue || '').trim().toLowerCase()
|
||||||
.trim()
|
|
||||||
.toLowerCase()
|
|
||||||
|
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -1,43 +1,48 @@
|
|||||||
import fs from 'node:fs'
|
import { Pool } from 'pg'
|
||||||
import path from 'node:path'
|
|
||||||
import { DatabaseSync } from 'node:sqlite'
|
|
||||||
|
|
||||||
import { runtimeConfig } from '../config/runtime.js'
|
import { runtimeConfig } from '../config/runtime.js'
|
||||||
|
|
||||||
let databaseInstance = null
|
let poolInstance = null
|
||||||
|
|
||||||
export function getDb() {
|
export function getDb() {
|
||||||
if (!databaseInstance) {
|
if (!poolInstance) {
|
||||||
const filePath = resolveDatabasePath()
|
poolInstance = new Pool({
|
||||||
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
connectionString: String(runtimeConfig.database?.url || '').trim(),
|
||||||
databaseInstance = new DatabaseSync(filePath)
|
ssl: runtimeConfig.database?.ssl ? { rejectUnauthorized: false } : false,
|
||||||
databaseInstance.exec('PRAGMA foreign_keys = ON;')
|
max: Number(runtimeConfig.database?.maxConnections || 10),
|
||||||
databaseInstance.exec('PRAGMA journal_mode = WAL;')
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return databaseInstance
|
return poolInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
export function runInTransaction(fn) {
|
export async function query(text, params = []) {
|
||||||
const db = getDb()
|
const pool = getDb()
|
||||||
db.exec('BEGIN')
|
return pool.query(text, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function withTransaction(fn) {
|
||||||
|
const client = await getDb().connect()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = fn(db)
|
await client.query('BEGIN')
|
||||||
db.exec('COMMIT')
|
const result = await fn(client)
|
||||||
|
await client.query('COMMIT')
|
||||||
return result
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
db.exec('ROLLBACK')
|
await client.query('ROLLBACK')
|
||||||
throw error
|
throw error
|
||||||
|
} finally {
|
||||||
|
client.release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveDatabasePath() {
|
export async function closeDb() {
|
||||||
const configured = String(runtimeConfig.database.filePath || '').trim()
|
if (!poolInstance) {
|
||||||
|
return
|
||||||
if (!configured) {
|
|
||||||
throw new Error('缺少数据库文件路径配置')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return path.resolve(configured)
|
const pool = poolInstance
|
||||||
|
poolInstance = null
|
||||||
|
await pool.end()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,28 @@
|
|||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
|
||||||
import process from 'node:process'
|
import process from 'node:process'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
import { getDb } from './client.js'
|
import { closeDb, query, withTransaction } from './client.js'
|
||||||
|
|
||||||
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url))
|
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url))
|
||||||
const MIGRATIONS_DIR = path.join(CURRENT_DIR, 'migrations')
|
const MIGRATIONS_DIR = path.join(CURRENT_DIR, 'migrations')
|
||||||
|
|
||||||
export function runDatabaseMigrations() {
|
export async function runDatabaseMigrations() {
|
||||||
const db = getDb()
|
|
||||||
const files = fs.readdirSync(MIGRATIONS_DIR)
|
const files = fs.readdirSync(MIGRATIONS_DIR)
|
||||||
.filter((name) => name.endsWith('.sql'))
|
.filter((name) => name.endsWith('.sql'))
|
||||||
.sort()
|
.sort()
|
||||||
|
|
||||||
db.exec(`
|
await query(`
|
||||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
filename TEXT NOT NULL UNIQUE,
|
filename TEXT NOT NULL UNIQUE,
|
||||||
applied_at TEXT NOT NULL
|
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
);
|
)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const appliedRows = db.prepare('SELECT filename FROM schema_migrations').all()
|
const appliedRows = await query('SELECT filename FROM schema_migrations')
|
||||||
const applied = new Set(appliedRows.map((row) => row.filename))
|
const applied = new Set(appliedRows.rows.map((row) => String(row.filename)))
|
||||||
const insertApplied = db.prepare(`
|
|
||||||
INSERT INTO schema_migrations (filename, applied_at)
|
|
||||||
VALUES (?, ?)
|
|
||||||
`)
|
|
||||||
|
|
||||||
for (const filename of files) {
|
for (const filename of files) {
|
||||||
if (applied.has(filename)) {
|
if (applied.has(filename)) {
|
||||||
@@ -35,8 +30,13 @@ export function runDatabaseMigrations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sql = fs.readFileSync(path.join(MIGRATIONS_DIR, filename), 'utf8')
|
const sql = fs.readFileSync(path.join(MIGRATIONS_DIR, filename), 'utf8')
|
||||||
db.exec(sql)
|
await withTransaction(async (client) => {
|
||||||
insertApplied.run(filename, new Date().toISOString())
|
await client.query(sql)
|
||||||
|
await client.query(
|
||||||
|
'INSERT INTO schema_migrations (filename, applied_at) VALUES ($1, NOW())',
|
||||||
|
[filename],
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,4 +44,12 @@ const currentFilePath = fileURLToPath(import.meta.url)
|
|||||||
|
|
||||||
if (process.argv[1] && path.resolve(process.argv[1]) === currentFilePath) {
|
if (process.argv[1] && path.resolve(process.argv[1]) === currentFilePath) {
|
||||||
runDatabaseMigrations()
|
runDatabaseMigrations()
|
||||||
|
.then(async () => {
|
||||||
|
await closeDb()
|
||||||
|
})
|
||||||
|
.catch(async (error) => {
|
||||||
|
console.error(error)
|
||||||
|
await closeDb()
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,272 @@
|
|||||||
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS admin_users (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
role TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS admin_audit_logs (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
actor_user_id BIGINT,
|
||||||
|
actor_username TEXT NOT NULL DEFAULT '',
|
||||||
|
actor_role TEXT NOT NULL DEFAULT '',
|
||||||
|
action TEXT NOT NULL,
|
||||||
|
target_type TEXT NOT NULL,
|
||||||
|
target_id TEXT NOT NULL,
|
||||||
|
payload_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_admin_audit_logs_created_at ON admin_audit_logs(created_at DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_admin_audit_logs_target ON admin_audit_logs(target_type, target_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS fulfillment_profiles (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
profile_key TEXT NOT NULL UNIQUE,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
executor_key TEXT NOT NULL,
|
||||||
|
requires_claim BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
auto_dispatch BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
inventory_strategy TEXT NOT NULL DEFAULT 'static_pool',
|
||||||
|
config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS fulfillment_profile_requirements (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
profile_id BIGINT NOT NULL REFERENCES fulfillment_profiles(id) ON DELETE CASCADE,
|
||||||
|
role_key TEXT NOT NULL,
|
||||||
|
credential_type TEXT NOT NULL,
|
||||||
|
quantity_per_unit INTEGER NOT NULL DEFAULT 1,
|
||||||
|
is_required BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL,
|
||||||
|
UNIQUE(profile_id, role_key)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS sku_fulfillment_bindings (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
sku_code TEXT NOT NULL,
|
||||||
|
provider TEXT NOT NULL DEFAULT '',
|
||||||
|
platform TEXT NOT NULL DEFAULT '',
|
||||||
|
shop_id TEXT NOT NULL DEFAULT '',
|
||||||
|
profile_id BIGINT NOT NULL REFERENCES fulfillment_profiles(id) ON DELETE CASCADE,
|
||||||
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
priority INTEGER NOT NULL DEFAULT 100,
|
||||||
|
config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sku_fulfillment_bindings_lookup
|
||||||
|
ON sku_fulfillment_bindings(sku_code, provider, platform, shop_id, enabled, priority);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS orders (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
provider TEXT NOT NULL,
|
||||||
|
platform TEXT NOT NULL,
|
||||||
|
shop_id TEXT NOT NULL DEFAULT '',
|
||||||
|
shop_name TEXT NOT NULL DEFAULT '',
|
||||||
|
platform_order_id TEXT NOT NULL,
|
||||||
|
order_status TEXT NOT NULL DEFAULT 'created',
|
||||||
|
pay_status TEXT NOT NULL DEFAULT 'unpaid',
|
||||||
|
buyer_id TEXT NOT NULL DEFAULT '',
|
||||||
|
buyer_name TEXT NOT NULL DEFAULT '',
|
||||||
|
receiver_contact TEXT NOT NULL DEFAULT '',
|
||||||
|
total_amount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
currency TEXT NOT NULL DEFAULT 'CNY',
|
||||||
|
raw_payload_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
paid_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL,
|
||||||
|
UNIQUE(provider, platform, shop_id, platform_order_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS order_items (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
order_id BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
|
||||||
|
sku_code TEXT NOT NULL,
|
||||||
|
sku_name TEXT NOT NULL DEFAULT '',
|
||||||
|
quantity INTEGER NOT NULL DEFAULT 1,
|
||||||
|
spec_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
item_snapshot_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_order_items_order_id ON order_items(order_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_order_items_sku_code ON order_items(sku_code);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS inventory_items (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
batch_no TEXT NOT NULL DEFAULT '',
|
||||||
|
sku_code TEXT NOT NULL,
|
||||||
|
credential_type TEXT NOT NULL,
|
||||||
|
display_value TEXT NOT NULL DEFAULT '',
|
||||||
|
payload_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
source_type TEXT NOT NULL DEFAULT 'static_import',
|
||||||
|
status TEXT NOT NULL DEFAULT 'available',
|
||||||
|
invalid_reason TEXT NOT NULL DEFAULT '',
|
||||||
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
consumed_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_inventory_items_unique_payload
|
||||||
|
ON inventory_items(sku_code, credential_type, md5(payload_json::text));
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_inventory_items_pool
|
||||||
|
ON inventory_items(sku_code, credential_type, status, id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS fulfillment_tasks (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
order_id BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
|
||||||
|
order_item_id BIGINT NOT NULL REFERENCES order_items(id) ON DELETE CASCADE,
|
||||||
|
unit_index INTEGER NOT NULL DEFAULT 1,
|
||||||
|
task_no TEXT NOT NULL UNIQUE,
|
||||||
|
provider TEXT NOT NULL,
|
||||||
|
platform TEXT NOT NULL,
|
||||||
|
shop_id TEXT NOT NULL DEFAULT '',
|
||||||
|
shop_name TEXT NOT NULL DEFAULT '',
|
||||||
|
platform_order_id TEXT NOT NULL,
|
||||||
|
profile_id BIGINT NOT NULL REFERENCES fulfillment_profiles(id) ON DELETE RESTRICT,
|
||||||
|
executor_key TEXT NOT NULL,
|
||||||
|
task_status TEXT NOT NULL DEFAULT 'pending_payment',
|
||||||
|
inventory_status TEXT NOT NULL DEFAULT 'pending',
|
||||||
|
delivery_status TEXT NOT NULL DEFAULT 'pending',
|
||||||
|
result_code TEXT NOT NULL DEFAULT '',
|
||||||
|
result_message TEXT NOT NULL DEFAULT '',
|
||||||
|
claim_token TEXT NOT NULL DEFAULT '',
|
||||||
|
claim_expires_at TIMESTAMPTZ,
|
||||||
|
automation_mode TEXT NOT NULL DEFAULT 'manual',
|
||||||
|
requires_claim BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
user_action_status TEXT NOT NULL DEFAULT 'not_required',
|
||||||
|
attempt_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_error TEXT NOT NULL DEFAULT '',
|
||||||
|
context_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
claimed_at TIMESTAMPTZ,
|
||||||
|
role_confirmed_at TIMESTAMPTZ,
|
||||||
|
redeemed_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL,
|
||||||
|
UNIQUE(order_item_id, unit_index)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_fulfillment_tasks_status ON fulfillment_tasks(task_status, delivery_status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_fulfillment_tasks_order ON fulfillment_tasks(order_id, order_item_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_fulfillment_tasks_claim_token ON fulfillment_tasks(claim_token);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS claim_tokens (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
task_id BIGINT NOT NULL UNIQUE REFERENCES fulfillment_tasks(id) ON DELETE CASCADE,
|
||||||
|
token TEXT NOT NULL UNIQUE,
|
||||||
|
status TEXT NOT NULL DEFAULT 'active',
|
||||||
|
expired_at TIMESTAMPTZ NOT NULL,
|
||||||
|
used_at TIMESTAMPTZ,
|
||||||
|
max_use_count INTEGER NOT NULL DEFAULT 1,
|
||||||
|
used_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_claim_tokens_status ON claim_tokens(status, expired_at);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS task_inventory_bindings (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
task_id BIGINT NOT NULL REFERENCES fulfillment_tasks(id) ON DELETE CASCADE,
|
||||||
|
inventory_item_id BIGINT NOT NULL REFERENCES inventory_items(id) ON DELETE RESTRICT,
|
||||||
|
role_key TEXT NOT NULL,
|
||||||
|
quantity INTEGER NOT NULL DEFAULT 1,
|
||||||
|
binding_status TEXT NOT NULL DEFAULT 'reserved',
|
||||||
|
consumed_at TIMESTAMPTZ,
|
||||||
|
released_at TIMESTAMPTZ,
|
||||||
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL,
|
||||||
|
UNIQUE(task_id, role_key, inventory_item_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_task_inventory_bindings_task ON task_inventory_bindings(task_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_task_inventory_bindings_inventory ON task_inventory_bindings(inventory_item_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS task_events (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
task_id BIGINT NOT NULL REFERENCES fulfillment_tasks(id) ON DELETE CASCADE,
|
||||||
|
event_type TEXT NOT NULL,
|
||||||
|
payload_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_task_events_task_created_at ON task_events(task_id, created_at DESC);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tencent_browser_contexts (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
task_id BIGINT NOT NULL UNIQUE REFERENCES fulfillment_tasks(id) ON DELETE CASCADE,
|
||||||
|
browser_session_id TEXT NOT NULL DEFAULT '',
|
||||||
|
login_type TEXT NOT NULL DEFAULT '',
|
||||||
|
nickname TEXT NOT NULL DEFAULT '',
|
||||||
|
role_id TEXT NOT NULL DEFAULT '',
|
||||||
|
role_name TEXT NOT NULL DEFAULT '',
|
||||||
|
area TEXT NOT NULL DEFAULT '',
|
||||||
|
partition_name TEXT NOT NULL DEFAULT '',
|
||||||
|
screenshot_path TEXT NOT NULL DEFAULT '',
|
||||||
|
artifacts_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
state_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS message_deliveries (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
provider TEXT NOT NULL DEFAULT '',
|
||||||
|
platform TEXT NOT NULL DEFAULT '',
|
||||||
|
shop_id TEXT NOT NULL DEFAULT '',
|
||||||
|
shop_name TEXT NOT NULL DEFAULT '',
|
||||||
|
channel TEXT NOT NULL,
|
||||||
|
order_id BIGINT REFERENCES orders(id) ON DELETE SET NULL,
|
||||||
|
task_id BIGINT REFERENCES fulfillment_tasks(id) ON DELETE SET NULL,
|
||||||
|
platform_order_id TEXT NOT NULL DEFAULT '',
|
||||||
|
recipient_key TEXT NOT NULL DEFAULT '',
|
||||||
|
message_content TEXT NOT NULL DEFAULT '',
|
||||||
|
claim_url TEXT NOT NULL DEFAULT '',
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending',
|
||||||
|
request_url TEXT NOT NULL DEFAULT '',
|
||||||
|
request_headers_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
request_body_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
response_status INTEGER NOT NULL DEFAULT 0,
|
||||||
|
response_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
error_message TEXT NOT NULL DEFAULT '',
|
||||||
|
sent_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_message_deliveries_task_channel ON message_deliveries(task_id, channel, created_at DESC);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS webhook_events (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
provider TEXT NOT NULL DEFAULT '',
|
||||||
|
platform TEXT NOT NULL DEFAULT '',
|
||||||
|
shop_id TEXT NOT NULL DEFAULT '',
|
||||||
|
shop_name TEXT NOT NULL DEFAULT '',
|
||||||
|
event_type TEXT NOT NULL,
|
||||||
|
event_key TEXT NOT NULL,
|
||||||
|
signature_valid BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
headers_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
query_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
body_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
processed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
process_error TEXT NOT NULL DEFAULT '',
|
||||||
|
related_order_id BIGINT REFERENCES orders(id) ON DELETE SET NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_webhook_events_lookup ON webhook_events(provider, platform, shop_id, event_key);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_webhook_events_processed ON webhook_events(processed, created_at DESC);
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
PRAGMA foreign_keys = ON;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS orders (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
platform TEXT NOT NULL,
|
|
||||||
platform_order_id TEXT NOT NULL,
|
|
||||||
order_status TEXT NOT NULL DEFAULT 'created',
|
|
||||||
pay_status TEXT NOT NULL DEFAULT 'unpaid',
|
|
||||||
buyer_id TEXT NOT NULL DEFAULT '',
|
|
||||||
buyer_name TEXT NOT NULL DEFAULT '',
|
|
||||||
receiver_contact TEXT NOT NULL DEFAULT '',
|
|
||||||
total_amount INTEGER NOT NULL DEFAULT 0,
|
|
||||||
currency TEXT NOT NULL DEFAULT 'CNY',
|
|
||||||
raw_payload_json TEXT NOT NULL DEFAULT '{}',
|
|
||||||
paid_at TEXT,
|
|
||||||
created_at TEXT NOT NULL,
|
|
||||||
updated_at TEXT NOT NULL,
|
|
||||||
UNIQUE(platform, platform_order_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS order_items (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
order_id INTEGER NOT NULL,
|
|
||||||
sku_code TEXT NOT NULL,
|
|
||||||
sku_name TEXT NOT NULL DEFAULT '',
|
|
||||||
quantity INTEGER NOT NULL DEFAULT 1,
|
|
||||||
spec_json TEXT NOT NULL DEFAULT '{}',
|
|
||||||
delivery_mode TEXT NOT NULL DEFAULT 'claim_link',
|
|
||||||
created_at TEXT NOT NULL,
|
|
||||||
updated_at TEXT NOT NULL,
|
|
||||||
FOREIGN KEY(order_id) REFERENCES orders(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_order_items_order_id ON order_items(order_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_order_items_sku_code ON order_items(sku_code);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS cdk_inventory (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
batch_no TEXT NOT NULL DEFAULT '',
|
|
||||||
sku_code TEXT NOT NULL,
|
|
||||||
cdk_code TEXT NOT NULL,
|
|
||||||
status TEXT NOT NULL DEFAULT 'available',
|
|
||||||
reserved_by_task_id INTEGER,
|
|
||||||
invalid_reason TEXT NOT NULL DEFAULT '',
|
|
||||||
delivered_at TEXT,
|
|
||||||
created_at TEXT NOT NULL,
|
|
||||||
updated_at TEXT NOT NULL,
|
|
||||||
UNIQUE(sku_code, cdk_code)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_cdk_inventory_sku_code ON cdk_inventory(sku_code);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_cdk_inventory_status ON cdk_inventory(status);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_cdk_inventory_reserved_task ON cdk_inventory(reserved_by_task_id);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS delivery_tasks (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
order_id INTEGER NOT NULL,
|
|
||||||
order_item_id INTEGER NOT NULL,
|
|
||||||
platform_order_id TEXT NOT NULL,
|
|
||||||
task_no TEXT NOT NULL,
|
|
||||||
task_status TEXT NOT NULL DEFAULT 'pending_payment',
|
|
||||||
login_type TEXT NOT NULL DEFAULT '',
|
|
||||||
claim_token_id INTEGER,
|
|
||||||
reserved_cdk_id INTEGER,
|
|
||||||
browser_session_id TEXT NOT NULL DEFAULT '',
|
|
||||||
nickname TEXT NOT NULL DEFAULT '',
|
|
||||||
role_id TEXT NOT NULL DEFAULT '',
|
|
||||||
role_name TEXT NOT NULL DEFAULT '',
|
|
||||||
area TEXT NOT NULL DEFAULT '',
|
|
||||||
partition_name TEXT NOT NULL DEFAULT '',
|
|
||||||
result_code TEXT NOT NULL DEFAULT '',
|
|
||||||
result_message TEXT NOT NULL DEFAULT '',
|
|
||||||
screenshot_path TEXT NOT NULL DEFAULT '',
|
|
||||||
artifacts_json TEXT NOT NULL DEFAULT '{}',
|
|
||||||
last_error TEXT NOT NULL DEFAULT '',
|
|
||||||
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
||||||
expires_at TEXT,
|
|
||||||
claimed_at TEXT,
|
|
||||||
role_confirmed_at TEXT,
|
|
||||||
redeemed_at TEXT,
|
|
||||||
created_at TEXT NOT NULL,
|
|
||||||
updated_at TEXT NOT NULL,
|
|
||||||
FOREIGN KEY(order_id) REFERENCES orders(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY(order_item_id) REFERENCES order_items(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY(reserved_cdk_id) REFERENCES cdk_inventory(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_delivery_tasks_task_no ON delivery_tasks(task_no);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_delivery_tasks_order_id ON delivery_tasks(order_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_delivery_tasks_order_item_id ON delivery_tasks(order_item_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_delivery_tasks_status ON delivery_tasks(task_status);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_delivery_tasks_platform_order_id ON delivery_tasks(platform_order_id);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS claim_tokens (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
task_id INTEGER NOT NULL,
|
|
||||||
token TEXT NOT NULL,
|
|
||||||
status TEXT NOT NULL DEFAULT 'active',
|
|
||||||
expired_at TEXT NOT NULL,
|
|
||||||
used_at TEXT,
|
|
||||||
max_use_count INTEGER NOT NULL DEFAULT 1,
|
|
||||||
used_count INTEGER NOT NULL DEFAULT 0,
|
|
||||||
created_at TEXT NOT NULL,
|
|
||||||
updated_at TEXT NOT NULL,
|
|
||||||
FOREIGN KEY(task_id) REFERENCES delivery_tasks(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_claim_tokens_token ON claim_tokens(token);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_claim_tokens_task_id ON claim_tokens(task_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_claim_tokens_status ON claim_tokens(status);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS webhook_events (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
platform TEXT NOT NULL,
|
|
||||||
event_type TEXT NOT NULL,
|
|
||||||
event_key TEXT NOT NULL,
|
|
||||||
signature_valid INTEGER NOT NULL DEFAULT 0,
|
|
||||||
headers_json TEXT NOT NULL DEFAULT '{}',
|
|
||||||
query_json TEXT NOT NULL DEFAULT '{}',
|
|
||||||
body_json TEXT NOT NULL DEFAULT '{}',
|
|
||||||
processed INTEGER NOT NULL DEFAULT 0,
|
|
||||||
process_error TEXT NOT NULL DEFAULT '',
|
|
||||||
related_order_id INTEGER,
|
|
||||||
created_at TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_webhook_events_platform_key ON webhook_events(platform, event_key);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_webhook_events_processed ON webhook_events(processed);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_webhook_events_created_at ON webhook_events(created_at);
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS admin_users (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
username TEXT NOT NULL UNIQUE,
|
|
||||||
password_hash TEXT NOT NULL,
|
|
||||||
role TEXT NOT NULL DEFAULT 'operator',
|
|
||||||
status TEXT NOT NULL DEFAULT 'active',
|
|
||||||
created_at TEXT NOT NULL,
|
|
||||||
updated_at TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_admin_users_role ON admin_users(role);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_admin_users_status ON admin_users(status);
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS admin_audit_logs (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
actor_user_id INTEGER NOT NULL,
|
|
||||||
actor_username TEXT NOT NULL,
|
|
||||||
actor_role TEXT NOT NULL,
|
|
||||||
action TEXT NOT NULL,
|
|
||||||
target_type TEXT NOT NULL,
|
|
||||||
target_id TEXT NOT NULL DEFAULT '',
|
|
||||||
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
||||||
created_at TEXT NOT NULL,
|
|
||||||
FOREIGN KEY (actor_user_id) REFERENCES admin_users(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_admin_audit_logs_actor_user_id ON admin_audit_logs(actor_user_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_admin_audit_logs_action ON admin_audit_logs(action);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_admin_audit_logs_target_type ON admin_audit_logs(target_type);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_admin_audit_logs_created_at ON admin_audit_logs(created_at);
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS message_deliveries (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
platform TEXT NOT NULL,
|
|
||||||
channel TEXT NOT NULL,
|
|
||||||
order_id INTEGER,
|
|
||||||
task_id INTEGER,
|
|
||||||
platform_order_id TEXT NOT NULL DEFAULT '',
|
|
||||||
recipient_key TEXT NOT NULL DEFAULT '',
|
|
||||||
message_content TEXT NOT NULL DEFAULT '',
|
|
||||||
claim_url TEXT NOT NULL DEFAULT '',
|
|
||||||
status TEXT NOT NULL DEFAULT 'pending',
|
|
||||||
request_url TEXT NOT NULL DEFAULT '',
|
|
||||||
request_headers_json TEXT NOT NULL DEFAULT '{}',
|
|
||||||
request_body_json TEXT NOT NULL DEFAULT '{}',
|
|
||||||
response_status INTEGER NOT NULL DEFAULT 0,
|
|
||||||
response_json TEXT NOT NULL DEFAULT '{}',
|
|
||||||
error_message TEXT NOT NULL DEFAULT '',
|
|
||||||
sent_at TEXT,
|
|
||||||
created_at TEXT NOT NULL,
|
|
||||||
updated_at TEXT NOT NULL,
|
|
||||||
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE SET NULL,
|
|
||||||
FOREIGN KEY (task_id) REFERENCES delivery_tasks(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_message_deliveries_order_id ON message_deliveries(order_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_message_deliveries_task_id ON message_deliveries(task_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_message_deliveries_platform_order_id ON message_deliveries(platform_order_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_message_deliveries_status ON message_deliveries(status);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_message_deliveries_created_at ON message_deliveries(created_at);
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
PRAGMA foreign_keys = OFF;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS orders__new (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
provider TEXT NOT NULL DEFAULT 'agiso',
|
|
||||||
platform TEXT NOT NULL,
|
|
||||||
shop_id TEXT NOT NULL DEFAULT '',
|
|
||||||
shop_name TEXT NOT NULL DEFAULT '',
|
|
||||||
platform_order_id TEXT NOT NULL,
|
|
||||||
order_status TEXT NOT NULL DEFAULT 'created',
|
|
||||||
pay_status TEXT NOT NULL DEFAULT 'unpaid',
|
|
||||||
buyer_id TEXT NOT NULL DEFAULT '',
|
|
||||||
buyer_name TEXT NOT NULL DEFAULT '',
|
|
||||||
receiver_contact TEXT NOT NULL DEFAULT '',
|
|
||||||
total_amount INTEGER NOT NULL DEFAULT 0,
|
|
||||||
currency TEXT NOT NULL DEFAULT 'CNY',
|
|
||||||
raw_payload_json TEXT NOT NULL DEFAULT '{}',
|
|
||||||
paid_at TEXT,
|
|
||||||
created_at TEXT NOT NULL,
|
|
||||||
updated_at TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT INTO orders__new (
|
|
||||||
id,
|
|
||||||
provider,
|
|
||||||
platform,
|
|
||||||
shop_id,
|
|
||||||
shop_name,
|
|
||||||
platform_order_id,
|
|
||||||
order_status,
|
|
||||||
pay_status,
|
|
||||||
buyer_id,
|
|
||||||
buyer_name,
|
|
||||||
receiver_contact,
|
|
||||||
total_amount,
|
|
||||||
currency,
|
|
||||||
raw_payload_json,
|
|
||||||
paid_at,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
'agiso',
|
|
||||||
platform,
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
platform_order_id,
|
|
||||||
order_status,
|
|
||||||
pay_status,
|
|
||||||
buyer_id,
|
|
||||||
buyer_name,
|
|
||||||
receiver_contact,
|
|
||||||
total_amount,
|
|
||||||
currency,
|
|
||||||
raw_payload_json,
|
|
||||||
paid_at,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
FROM orders;
|
|
||||||
|
|
||||||
DROP TABLE orders;
|
|
||||||
ALTER TABLE orders__new RENAME TO orders;
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_orders_provider_platform_shop_order
|
|
||||||
ON orders(provider, platform, shop_id, platform_order_id);
|
|
||||||
|
|
||||||
ALTER TABLE webhook_events ADD COLUMN provider TEXT NOT NULL DEFAULT 'agiso';
|
|
||||||
ALTER TABLE webhook_events ADD COLUMN shop_id TEXT NOT NULL DEFAULT '';
|
|
||||||
ALTER TABLE webhook_events ADD COLUMN shop_name TEXT NOT NULL DEFAULT '';
|
|
||||||
|
|
||||||
UPDATE webhook_events
|
|
||||||
SET provider = CASE
|
|
||||||
WHEN trim(provider) = '' THEN 'agiso'
|
|
||||||
ELSE provider
|
|
||||||
END;
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_webhook_events_provider_platform_shop
|
|
||||||
ON webhook_events(provider, platform, shop_id);
|
|
||||||
|
|
||||||
ALTER TABLE message_deliveries ADD COLUMN provider TEXT NOT NULL DEFAULT 'agiso';
|
|
||||||
ALTER TABLE message_deliveries ADD COLUMN shop_id TEXT NOT NULL DEFAULT '';
|
|
||||||
ALTER TABLE message_deliveries ADD COLUMN shop_name TEXT NOT NULL DEFAULT '';
|
|
||||||
|
|
||||||
UPDATE message_deliveries
|
|
||||||
SET provider = CASE
|
|
||||||
WHEN trim(provider) = '' THEN 'agiso'
|
|
||||||
ELSE provider
|
|
||||||
END;
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_message_deliveries_provider_platform_shop
|
|
||||||
ON message_deliveries(provider, platform, shop_id);
|
|
||||||
|
|
||||||
PRAGMA foreign_keys = ON;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
UPDATE orders
|
|
||||||
SET total_amount = total_amount * 100
|
|
||||||
WHERE total_amount != 0;
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
UPDATE orders
|
|
||||||
SET platform = 'xianyu'
|
|
||||||
WHERE lower(trim(platform)) = 'aldsidle';
|
|
||||||
|
|
||||||
UPDATE webhook_events
|
|
||||||
SET platform = 'xianyu'
|
|
||||||
WHERE lower(trim(platform)) = 'aldsidle';
|
|
||||||
|
|
||||||
UPDATE message_deliveries
|
|
||||||
SET platform = 'xianyu'
|
|
||||||
WHERE lower(trim(platform)) = 'aldsidle';
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { getDb } from '../db/client.js'
|
import { query } from '../db/client.js'
|
||||||
|
|
||||||
export function createAdminAuditLog(input) {
|
export async function createAdminAuditLog(input) {
|
||||||
const result = getDb().prepare(`
|
const result = await query(
|
||||||
|
`
|
||||||
INSERT INTO admin_audit_logs (
|
INSERT INTO admin_audit_logs (
|
||||||
actor_user_id,
|
actor_user_id,
|
||||||
actor_username,
|
actor_username,
|
||||||
@@ -11,76 +12,83 @@ export function createAdminAuditLog(input) {
|
|||||||
target_id,
|
target_id,
|
||||||
payload_json,
|
payload_json,
|
||||||
created_at
|
created_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8)
|
||||||
`).run(
|
RETURNING *
|
||||||
input.actorUserId,
|
`,
|
||||||
input.actorUsername,
|
[
|
||||||
input.actorRole,
|
input.actorUserId || null,
|
||||||
|
input.actorUsername || '',
|
||||||
|
input.actorRole || '',
|
||||||
input.action,
|
input.action,
|
||||||
input.targetType,
|
input.targetType,
|
||||||
input.targetId,
|
input.targetId,
|
||||||
input.payloadJson,
|
input.payloadJson || '{}',
|
||||||
input.createdAt,
|
input.createdAt,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
return getAdminAuditLogById(Number(result.lastInsertRowid))
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAdminAuditLogById(logId) {
|
export async function getAdminAuditLogById(logId) {
|
||||||
return getDb().prepare('SELECT * FROM admin_audit_logs WHERE id = ? LIMIT 1').get(logId) || null
|
const result = await query('SELECT * FROM admin_audit_logs WHERE id = $1 LIMIT 1', [Number(logId)])
|
||||||
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listAdminAuditLogs(query = {}) {
|
export async function listAdminAuditLogs(queryInput = {}) {
|
||||||
const conditions = []
|
const conditions = []
|
||||||
const values = []
|
const params = []
|
||||||
|
|
||||||
if (query.actorUsername) {
|
if (queryInput.actorUsername) {
|
||||||
conditions.push('actor_username = ?')
|
params.push(queryInput.actorUsername)
|
||||||
values.push(query.actorUsername)
|
conditions.push(`actor_username = $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.action) {
|
if (queryInput.action) {
|
||||||
conditions.push('action = ?')
|
params.push(queryInput.action)
|
||||||
values.push(query.action)
|
conditions.push(`action = $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.targetType) {
|
if (queryInput.targetType) {
|
||||||
conditions.push('target_type = ?')
|
params.push(queryInput.targetType)
|
||||||
values.push(query.targetType)
|
conditions.push(`target_type = $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.dateFrom) {
|
if (queryInput.dateFrom) {
|
||||||
conditions.push('created_at >= ?')
|
params.push(queryInput.dateFrom)
|
||||||
values.push(query.dateFrom)
|
conditions.push(`created_at >= $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.dateTo) {
|
if (queryInput.dateTo) {
|
||||||
conditions.push('created_at <= ?')
|
params.push(queryInput.dateTo)
|
||||||
values.push(query.dateTo)
|
conditions.push(`created_at <= $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
||||||
const page = Number(query.page) || 1
|
const page = Number(queryInput.page) || 1
|
||||||
const pageSize = Number(query.pageSize) || 20
|
const pageSize = Number(queryInput.pageSize) || 20
|
||||||
const offset = (page - 1) * pageSize
|
const offset = (page - 1) * pageSize
|
||||||
const db = getDb()
|
|
||||||
|
|
||||||
const items = db.prepare(`
|
const totalResult = await query(
|
||||||
|
`SELECT COUNT(*)::int AS total FROM admin_audit_logs ${whereClause}`,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
|
||||||
|
params.push(pageSize)
|
||||||
|
params.push(offset)
|
||||||
|
const itemsResult = await query(
|
||||||
|
`
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM admin_audit_logs
|
FROM admin_audit_logs
|
||||||
${whereClause}
|
${whereClause}
|
||||||
ORDER BY id DESC
|
ORDER BY id DESC
|
||||||
LIMIT ? OFFSET ?
|
LIMIT $${params.length - 1} OFFSET $${params.length}
|
||||||
`).all(...values, pageSize, offset)
|
`,
|
||||||
|
params,
|
||||||
const totalRow = db.prepare(`
|
)
|
||||||
SELECT COUNT(*) AS total
|
|
||||||
FROM admin_audit_logs
|
|
||||||
${whereClause}
|
|
||||||
`).get(...values)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items,
|
items: itemsResult.rows,
|
||||||
total: Number(totalRow?.total || 0),
|
total: Number(totalResult.rows[0]?.total || 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
import { getDb } from '../db/client.js'
|
import { query } from '../db/client.js'
|
||||||
|
|
||||||
export function getAdminUserByUsername(username) {
|
export async function getAdminUserById(userId) {
|
||||||
return getDb().prepare('SELECT * FROM admin_users WHERE username = ? LIMIT 1').get(username) || null
|
const result = await query(
|
||||||
|
'SELECT * FROM admin_users WHERE id = $1 LIMIT 1',
|
||||||
|
[Number(userId)],
|
||||||
|
)
|
||||||
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAdminUserById(userId) {
|
export async function getAdminUserByUsername(username) {
|
||||||
return getDb().prepare('SELECT * FROM admin_users WHERE id = ? LIMIT 1').get(userId) || null
|
const result = await query(
|
||||||
|
'SELECT * FROM admin_users WHERE username = $1 LIMIT 1',
|
||||||
|
[String(username || '').trim().toLowerCase()],
|
||||||
|
)
|
||||||
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createAdminUser(input) {
|
export async function createAdminUser(input) {
|
||||||
const result = getDb().prepare(`
|
const result = await query(
|
||||||
|
`
|
||||||
INSERT INTO admin_users (
|
INSERT INTO admin_users (
|
||||||
username,
|
username,
|
||||||
password_hash,
|
password_hash,
|
||||||
@@ -17,101 +26,99 @@ export function createAdminUser(input) {
|
|||||||
status,
|
status,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?)
|
) VALUES ($1, $2, $3, $4, $5, $6)
|
||||||
`).run(
|
RETURNING *
|
||||||
|
`,
|
||||||
|
[
|
||||||
input.username,
|
input.username,
|
||||||
input.passwordHash,
|
input.passwordHash,
|
||||||
input.role,
|
input.role,
|
||||||
input.status,
|
input.status,
|
||||||
input.createdAt,
|
input.createdAt,
|
||||||
input.updatedAt,
|
input.updatedAt,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
return getAdminUserById(Number(result.lastInsertRowid))
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listAdminUsers(query = {}) {
|
export async function updateAdminUser(userId, patch) {
|
||||||
const conditions = []
|
const current = await getAdminUserById(userId)
|
||||||
const values = []
|
|
||||||
|
|
||||||
if (query.username) {
|
|
||||||
conditions.push('username LIKE ?')
|
|
||||||
values.push(`%${query.username}%`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.role) {
|
|
||||||
conditions.push('role = ?')
|
|
||||||
values.push(query.role)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.status) {
|
|
||||||
conditions.push('status = ?')
|
|
||||||
values.push(query.status)
|
|
||||||
}
|
|
||||||
|
|
||||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
|
||||||
const page = Number(query.page) || 1
|
|
||||||
const pageSize = Number(query.pageSize) || 20
|
|
||||||
const offset = (page - 1) * pageSize
|
|
||||||
const db = getDb()
|
|
||||||
|
|
||||||
const items = db.prepare(`
|
|
||||||
SELECT *
|
|
||||||
FROM admin_users
|
|
||||||
${whereClause}
|
|
||||||
ORDER BY id DESC
|
|
||||||
LIMIT ? OFFSET ?
|
|
||||||
`).all(...values, pageSize, offset)
|
|
||||||
|
|
||||||
const totalRow = db.prepare(`
|
|
||||||
SELECT COUNT(*) AS total
|
|
||||||
FROM admin_users
|
|
||||||
${whereClause}
|
|
||||||
`).get(...values)
|
|
||||||
|
|
||||||
return {
|
|
||||||
items,
|
|
||||||
total: Number(totalRow?.total || 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateAdminUser(userId, patch = {}) {
|
|
||||||
const current = getAdminUserById(userId)
|
|
||||||
|
|
||||||
if (!current) {
|
if (!current) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = {
|
const next = { ...current, ...patch }
|
||||||
username: Object.prototype.hasOwnProperty.call(patch, 'username') ? patch.username : current.username,
|
const result = await query(
|
||||||
password_hash: Object.prototype.hasOwnProperty.call(patch, 'password_hash') ? patch.password_hash : current.password_hash,
|
`
|
||||||
role: Object.prototype.hasOwnProperty.call(patch, 'role') ? patch.role : current.role,
|
|
||||||
status: Object.prototype.hasOwnProperty.call(patch, 'status') ? patch.status : current.status,
|
|
||||||
updated_at: Object.prototype.hasOwnProperty.call(patch, 'updated_at') ? patch.updated_at : current.updated_at,
|
|
||||||
}
|
|
||||||
|
|
||||||
getDb().prepare(`
|
|
||||||
UPDATE admin_users
|
UPDATE admin_users
|
||||||
SET username = ?, password_hash = ?, role = ?, status = ?, updated_at = ?
|
SET
|
||||||
WHERE id = ?
|
username = $1,
|
||||||
`).run(
|
password_hash = $2,
|
||||||
|
role = $3,
|
||||||
|
status = $4,
|
||||||
|
updated_at = $5
|
||||||
|
WHERE id = $6
|
||||||
|
RETURNING *
|
||||||
|
`,
|
||||||
|
[
|
||||||
next.username,
|
next.username,
|
||||||
next.password_hash,
|
next.password_hash,
|
||||||
next.role,
|
next.role,
|
||||||
next.status,
|
next.status,
|
||||||
next.updated_at,
|
next.updated_at,
|
||||||
userId,
|
Number(userId),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
return getAdminUserById(userId)
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function countActiveAdminUsers() {
|
export async function listAdminUsers({ page = 1, pageSize = 20, username = '', role = '', status = '' } = {}) {
|
||||||
const row = getDb().prepare(`
|
const offset = (page - 1) * pageSize
|
||||||
SELECT COUNT(*) AS total
|
const filters = []
|
||||||
|
const params = []
|
||||||
|
|
||||||
|
if (username) {
|
||||||
|
params.push(`%${username}%`)
|
||||||
|
filters.push(`username ILIKE $${params.length}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role) {
|
||||||
|
params.push(role)
|
||||||
|
filters.push(`role = $${params.length}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
params.push(status)
|
||||||
|
filters.push(`status = $${params.length}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
||||||
|
const totalResult = await query(`SELECT COUNT(*)::int AS total FROM admin_users ${whereClause}`, params)
|
||||||
|
|
||||||
|
params.push(pageSize)
|
||||||
|
params.push(offset)
|
||||||
|
const itemsResult = await query(
|
||||||
|
`
|
||||||
|
SELECT *
|
||||||
FROM admin_users
|
FROM admin_users
|
||||||
WHERE role = 'admin' AND status = 'active'
|
${whereClause}
|
||||||
`).get()
|
ORDER BY id DESC
|
||||||
|
LIMIT $${params.length - 1} OFFSET $${params.length}
|
||||||
|
`,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
|
||||||
return Number(row?.total || 0)
|
return {
|
||||||
|
items: itemsResult.rows,
|
||||||
|
total: Number(totalResult.rows[0]?.total || 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function countActiveAdminUsers() {
|
||||||
|
const result = await query(
|
||||||
|
`SELECT COUNT(*)::int AS total FROM admin_users WHERE role = 'admin' AND status = 'active'`,
|
||||||
|
)
|
||||||
|
return Number(result.rows[0]?.total || 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,118 @@
|
|||||||
import { getDb } from '../db/client.js'
|
import { query, withTransaction } from '../db/client.js'
|
||||||
|
|
||||||
export function findFirstAvailableCdkBySkuCode(skuCode) {
|
const CDK_SELECT = `
|
||||||
return getDb().prepare(`
|
SELECT
|
||||||
|
ii.id,
|
||||||
|
ii.sku_code,
|
||||||
|
ii.batch_no,
|
||||||
|
ii.credential_type,
|
||||||
|
ii.display_value AS cdk_code,
|
||||||
|
ii.display_value,
|
||||||
|
ii.status,
|
||||||
|
ii.invalid_reason,
|
||||||
|
ii.consumed_at AS delivered_at,
|
||||||
|
ii.created_at,
|
||||||
|
ii.updated_at,
|
||||||
|
tib.task_id AS reserved_by_task_id,
|
||||||
|
ft.task_no AS reserved_by_task_no,
|
||||||
|
ft.platform_order_id
|
||||||
|
FROM inventory_items ii
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM cdk_inventory
|
FROM task_inventory_bindings
|
||||||
WHERE sku_code = ? AND status = 'available'
|
WHERE inventory_item_id = ii.id AND binding_status IN ('reserved', 'consumed')
|
||||||
ORDER BY id ASC
|
ORDER BY id DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`).get(skuCode) || null
|
) tib ON TRUE
|
||||||
|
LEFT JOIN fulfillment_tasks ft ON ft.id = tib.task_id
|
||||||
|
`
|
||||||
|
|
||||||
|
export async function findFirstAvailableCdkBySkuCode(skuCode, credentialType = 'tencent_code') {
|
||||||
|
const result = await query(
|
||||||
|
`${CDK_SELECT}
|
||||||
|
WHERE ii.sku_code = $1 AND ii.credential_type = $2 AND ii.status = 'available'
|
||||||
|
ORDER BY ii.id ASC
|
||||||
|
LIMIT 1`,
|
||||||
|
[skuCode, credentialType],
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assignReservedCdk(cdkId, taskId, updatedAt) {
|
export async function assignReservedCdk(cdkId, taskId, updatedAt, roleKey = 'primary_code') {
|
||||||
getDb().prepare(`
|
return withTransaction(async (client) => {
|
||||||
UPDATE cdk_inventory
|
const inventoryResult = await client.query(
|
||||||
SET
|
`
|
||||||
status = 'reserved',
|
UPDATE inventory_items
|
||||||
reserved_by_task_id = ?,
|
SET status = 'reserved', updated_at = $1
|
||||||
updated_at = ?
|
WHERE id = $2 AND status = 'available'
|
||||||
WHERE id = ? AND status = 'available'
|
RETURNING id
|
||||||
`).run(taskId, updatedAt, cdkId)
|
`,
|
||||||
|
[updatedAt, Number(cdkId)],
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!inventoryResult.rows[0]) {
|
||||||
return getCdkById(cdkId)
|
return getCdkById(cdkId)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCdkById(cdkId) {
|
await client.query(
|
||||||
return getDb().prepare('SELECT * FROM cdk_inventory WHERE id = ? LIMIT 1').get(cdkId) || null
|
`
|
||||||
}
|
INSERT INTO task_inventory_bindings (
|
||||||
|
task_id,
|
||||||
export function markCdkDelivered(cdkId, deliveredAt) {
|
inventory_item_id,
|
||||||
getDb().prepare(`
|
role_key,
|
||||||
UPDATE cdk_inventory
|
quantity,
|
||||||
SET
|
binding_status,
|
||||||
status = 'delivered',
|
metadata_json,
|
||||||
delivered_at = ?,
|
created_at,
|
||||||
updated_at = ?
|
updated_at
|
||||||
WHERE id = ?
|
) VALUES ($1, $2, $3, 1, 'reserved', '{}'::jsonb, $4, $4)
|
||||||
`).run(deliveredAt, deliveredAt, cdkId)
|
ON CONFLICT (task_id, role_key, inventory_item_id) DO UPDATE
|
||||||
|
SET binding_status = 'reserved', updated_at = EXCLUDED.updated_at, released_at = NULL
|
||||||
|
`,
|
||||||
|
[Number(taskId), Number(cdkId), roleKey, updatedAt],
|
||||||
|
)
|
||||||
|
|
||||||
return getCdkById(cdkId)
|
return getCdkById(cdkId)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listCdks({
|
export async function getCdkById(cdkId) {
|
||||||
|
const result = await query(
|
||||||
|
`${CDK_SELECT}
|
||||||
|
WHERE ii.id = $1
|
||||||
|
LIMIT 1`,
|
||||||
|
[Number(cdkId)],
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.rows[0] || null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function markCdkDelivered(cdkId, deliveredAt) {
|
||||||
|
return withTransaction(async (client) => {
|
||||||
|
await client.query(
|
||||||
|
`
|
||||||
|
UPDATE inventory_items
|
||||||
|
SET status = 'consumed', consumed_at = $1, updated_at = $1
|
||||||
|
WHERE id = $2
|
||||||
|
`,
|
||||||
|
[deliveredAt, Number(cdkId)],
|
||||||
|
)
|
||||||
|
|
||||||
|
await client.query(
|
||||||
|
`
|
||||||
|
UPDATE task_inventory_bindings
|
||||||
|
SET binding_status = 'consumed', consumed_at = $1, updated_at = $1
|
||||||
|
WHERE inventory_item_id = $2 AND binding_status = 'reserved'
|
||||||
|
`,
|
||||||
|
[deliveredAt, Number(cdkId)],
|
||||||
|
)
|
||||||
|
|
||||||
|
return getCdkById(cdkId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listCdks({
|
||||||
page = 1,
|
page = 1,
|
||||||
pageSize = 20,
|
pageSize = 20,
|
||||||
skuCode = '',
|
skuCode = '',
|
||||||
@@ -52,63 +124,78 @@ export function listCdks({
|
|||||||
const params = []
|
const params = []
|
||||||
|
|
||||||
if (skuCode) {
|
if (skuCode) {
|
||||||
filters.push('sku_code = ?')
|
|
||||||
params.push(skuCode)
|
params.push(skuCode)
|
||||||
|
filters.push(`ii.sku_code = $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
filters.push('status = ?')
|
params.push(status === 'delivered' ? 'consumed' : status)
|
||||||
params.push(status)
|
filters.push(`ii.status = $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (batchNo) {
|
if (batchNo) {
|
||||||
filters.push('batch_no LIKE ?')
|
|
||||||
params.push(`%${batchNo}%`)
|
params.push(`%${batchNo}%`)
|
||||||
|
filters.push(`ii.batch_no ILIKE $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
||||||
const db = getDb()
|
const totalResult = await query(
|
||||||
const totalRow = db.prepare(`
|
`SELECT COUNT(*)::int AS total FROM inventory_items ii ${whereClause}`,
|
||||||
SELECT COUNT(*) AS total
|
params,
|
||||||
FROM cdk_inventory
|
)
|
||||||
${whereClause}
|
|
||||||
`).get(...params)
|
|
||||||
|
|
||||||
const items = db.prepare(`
|
params.push(pageSize)
|
||||||
SELECT *
|
params.push(offset)
|
||||||
FROM cdk_inventory
|
const itemsResult = await query(
|
||||||
|
`${CDK_SELECT}
|
||||||
${whereClause}
|
${whereClause}
|
||||||
ORDER BY id DESC
|
ORDER BY ii.id DESC
|
||||||
LIMIT ? OFFSET ?
|
LIMIT $${params.length - 1} OFFSET $${params.length}`,
|
||||||
`).all(...params, pageSize, offset)
|
params,
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items,
|
items: itemsResult.rows,
|
||||||
total: Number(totalRow?.total || 0),
|
total: Number(totalResult.rows[0]?.total || 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createCdks(rows) {
|
export async function createCdks(rows) {
|
||||||
const db = getDb()
|
|
||||||
const stmt = db.prepare(`
|
|
||||||
INSERT OR IGNORE INTO cdk_inventory (
|
|
||||||
batch_no,
|
|
||||||
sku_code,
|
|
||||||
cdk_code,
|
|
||||||
status,
|
|
||||||
reserved_by_task_id,
|
|
||||||
invalid_reason,
|
|
||||||
delivered_at,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
) VALUES (?, ?, ?, 'available', NULL, '', NULL, ?, ?)
|
|
||||||
`)
|
|
||||||
|
|
||||||
let created = 0
|
let created = 0
|
||||||
|
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
const result = stmt.run(row.batchNo, row.skuCode, row.cdkCode, row.createdAt, row.updatedAt)
|
const payloadJson = JSON.stringify(row.payload || { code: row.cdkCode })
|
||||||
if (Number(result.changes || 0) > 0) {
|
const displayValue = String(row.cdkCode || row.displayValue || '').trim()
|
||||||
|
const result = await query(
|
||||||
|
`
|
||||||
|
INSERT INTO inventory_items (
|
||||||
|
batch_no,
|
||||||
|
sku_code,
|
||||||
|
credential_type,
|
||||||
|
display_value,
|
||||||
|
payload_json,
|
||||||
|
source_type,
|
||||||
|
status,
|
||||||
|
invalid_reason,
|
||||||
|
metadata_json,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES ($1, $2, $3, $4, $5::jsonb, 'static_import', 'available', '', '{}'::jsonb, $6, $7)
|
||||||
|
ON CONFLICT DO NOTHING
|
||||||
|
RETURNING id
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
row.batchNo || '',
|
||||||
|
row.skuCode,
|
||||||
|
row.credentialType || 'tencent_code',
|
||||||
|
displayValue,
|
||||||
|
payloadJson,
|
||||||
|
row.createdAt,
|
||||||
|
row.updatedAt,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result.rows[0]?.id) {
|
||||||
created += 1
|
created += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,28 +203,44 @@ export function createCdks(rows) {
|
|||||||
return created
|
return created
|
||||||
}
|
}
|
||||||
|
|
||||||
export function releaseReservedCdk(cdkId, updatedAt) {
|
export async function releaseReservedCdk(cdkId, updatedAt) {
|
||||||
getDb().prepare(`
|
return withTransaction(async (client) => {
|
||||||
UPDATE cdk_inventory
|
await client.query(
|
||||||
SET
|
`
|
||||||
status = 'available',
|
UPDATE task_inventory_bindings
|
||||||
reserved_by_task_id = NULL,
|
SET binding_status = 'released', released_at = $1, updated_at = $1
|
||||||
updated_at = ?
|
WHERE inventory_item_id = $2 AND binding_status = 'reserved'
|
||||||
WHERE id = ? AND status = 'reserved'
|
`,
|
||||||
`).run(updatedAt, cdkId)
|
[updatedAt, Number(cdkId)],
|
||||||
|
)
|
||||||
|
|
||||||
|
await client.query(
|
||||||
|
`
|
||||||
|
UPDATE inventory_items
|
||||||
|
SET status = 'available', updated_at = $1
|
||||||
|
WHERE id = $2 AND status = 'reserved'
|
||||||
|
`,
|
||||||
|
[updatedAt, Number(cdkId)],
|
||||||
|
)
|
||||||
|
|
||||||
|
return getCdkById(cdkId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function invalidateCdk(cdkId, invalidReason, updatedAt) {
|
||||||
|
const result = await query(
|
||||||
|
`
|
||||||
|
UPDATE inventory_items
|
||||||
|
SET status = 'invalid', invalid_reason = $1, updated_at = $2
|
||||||
|
WHERE id = $3 AND status = 'available'
|
||||||
|
RETURNING id
|
||||||
|
`,
|
||||||
|
[invalidReason, updatedAt, Number(cdkId)],
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!result.rows[0]) {
|
||||||
return getCdkById(cdkId)
|
return getCdkById(cdkId)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invalidateCdk(cdkId, invalidReason, updatedAt) {
|
|
||||||
getDb().prepare(`
|
|
||||||
UPDATE cdk_inventory
|
|
||||||
SET
|
|
||||||
status = 'invalid',
|
|
||||||
invalid_reason = ?,
|
|
||||||
updated_at = ?
|
|
||||||
WHERE id = ? AND status = 'available'
|
|
||||||
`).run(invalidReason, updatedAt, cdkId)
|
|
||||||
|
|
||||||
return getCdkById(cdkId)
|
return getCdkById(cdkId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { getDb } from '../db/client.js'
|
import { query } from '../db/client.js'
|
||||||
|
|
||||||
export function createClaimToken(input) {
|
export async function createClaimToken(input) {
|
||||||
const result = getDb().prepare(`
|
const result = await query(
|
||||||
|
`
|
||||||
INSERT INTO claim_tokens (
|
INSERT INTO claim_tokens (
|
||||||
task_id,
|
task_id,
|
||||||
token,
|
token,
|
||||||
@@ -12,58 +13,65 @@ export function createClaimToken(input) {
|
|||||||
used_count,
|
used_count,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||||
`).run(
|
RETURNING *
|
||||||
|
`,
|
||||||
|
[
|
||||||
input.taskId,
|
input.taskId,
|
||||||
input.token,
|
input.token,
|
||||||
input.status,
|
input.status,
|
||||||
input.expiredAt,
|
input.expiredAt,
|
||||||
input.usedAt,
|
input.usedAt || null,
|
||||||
input.maxUseCount,
|
input.maxUseCount,
|
||||||
input.usedCount,
|
input.usedCount,
|
||||||
input.createdAt,
|
input.createdAt,
|
||||||
input.updatedAt,
|
input.updatedAt,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
return getClaimTokenById(Number(result.lastInsertRowid))
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getClaimTokenById(tokenId) {
|
export async function getClaimTokenById(tokenId) {
|
||||||
return getDb().prepare('SELECT * FROM claim_tokens WHERE id = ? LIMIT 1').get(tokenId) || null
|
const result = await query('SELECT * FROM claim_tokens WHERE id = $1 LIMIT 1', [Number(tokenId)])
|
||||||
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findClaimTokenByToken(token) {
|
export async function findClaimTokenByToken(token) {
|
||||||
return getDb().prepare('SELECT * FROM claim_tokens WHERE token = ? LIMIT 1').get(token) || null
|
const result = await query('SELECT * FROM claim_tokens WHERE token = $1 LIMIT 1', [String(token || '').trim()])
|
||||||
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateClaimToken(tokenId, patch) {
|
export async function updateClaimToken(tokenId, patch) {
|
||||||
const current = getClaimTokenById(tokenId)
|
const current = await getClaimTokenById(tokenId)
|
||||||
|
|
||||||
if (!current) {
|
if (!current) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = { ...current, ...patch }
|
const next = { ...current, ...patch }
|
||||||
|
const result = await query(
|
||||||
getDb().prepare(`
|
`
|
||||||
UPDATE claim_tokens
|
UPDATE claim_tokens
|
||||||
SET
|
SET
|
||||||
status = ?,
|
status = $1,
|
||||||
expired_at = ?,
|
expired_at = $2,
|
||||||
used_at = ?,
|
used_at = $3,
|
||||||
max_use_count = ?,
|
max_use_count = $4,
|
||||||
used_count = ?,
|
used_count = $5,
|
||||||
updated_at = ?
|
updated_at = $6
|
||||||
WHERE id = ?
|
WHERE id = $7
|
||||||
`).run(
|
RETURNING *
|
||||||
|
`,
|
||||||
|
[
|
||||||
next.status,
|
next.status,
|
||||||
next.expired_at,
|
next.expired_at,
|
||||||
next.used_at,
|
next.used_at,
|
||||||
next.max_use_count,
|
next.max_use_count,
|
||||||
next.used_count,
|
next.used_count,
|
||||||
next.updated_at,
|
next.updated_at,
|
||||||
tokenId,
|
Number(tokenId),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
return getClaimTokenById(tokenId)
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,203 @@
|
|||||||
|
import { query, withTransaction } from '../db/client.js'
|
||||||
|
|
||||||
|
export async function getFulfillmentProfileByKey(profileKey) {
|
||||||
|
const result = await query(
|
||||||
|
'SELECT * FROM fulfillment_profiles WHERE profile_key = $1 LIMIT 1',
|
||||||
|
[String(profileKey || '').trim()],
|
||||||
|
)
|
||||||
|
return result.rows[0] || null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function upsertFulfillmentProfile(input) {
|
||||||
|
const result = await query(
|
||||||
|
`
|
||||||
|
INSERT INTO fulfillment_profiles (
|
||||||
|
profile_key,
|
||||||
|
name,
|
||||||
|
executor_key,
|
||||||
|
requires_claim,
|
||||||
|
auto_dispatch,
|
||||||
|
inventory_strategy,
|
||||||
|
config_json,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8, $9)
|
||||||
|
ON CONFLICT (profile_key) DO UPDATE
|
||||||
|
SET
|
||||||
|
name = EXCLUDED.name,
|
||||||
|
executor_key = EXCLUDED.executor_key,
|
||||||
|
requires_claim = EXCLUDED.requires_claim,
|
||||||
|
auto_dispatch = EXCLUDED.auto_dispatch,
|
||||||
|
inventory_strategy = EXCLUDED.inventory_strategy,
|
||||||
|
config_json = EXCLUDED.config_json,
|
||||||
|
updated_at = EXCLUDED.updated_at
|
||||||
|
RETURNING *
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
input.profileKey,
|
||||||
|
input.name,
|
||||||
|
input.executorKey,
|
||||||
|
Boolean(input.requiresClaim),
|
||||||
|
Boolean(input.autoDispatch),
|
||||||
|
input.inventoryStrategy || 'static_pool',
|
||||||
|
input.configJson || '{}',
|
||||||
|
input.createdAt,
|
||||||
|
input.updatedAt,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.rows[0] || null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function replaceFulfillmentProfileRequirements(profileId, requirements = [], timestamp) {
|
||||||
|
await withTransaction(async (client) => {
|
||||||
|
await client.query(
|
||||||
|
'DELETE FROM fulfillment_profile_requirements WHERE profile_id = $1',
|
||||||
|
[Number(profileId)],
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const requirement of requirements) {
|
||||||
|
await client.query(
|
||||||
|
`
|
||||||
|
INSERT INTO fulfillment_profile_requirements (
|
||||||
|
profile_id,
|
||||||
|
role_key,
|
||||||
|
credential_type,
|
||||||
|
quantity_per_unit,
|
||||||
|
is_required,
|
||||||
|
config_json,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7, $8)
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
Number(profileId),
|
||||||
|
requirement.roleKey,
|
||||||
|
requirement.credentialType,
|
||||||
|
Number(requirement.quantityPerUnit || 1),
|
||||||
|
requirement.isRequired !== false,
|
||||||
|
requirement.configJson || '{}',
|
||||||
|
timestamp,
|
||||||
|
timestamp,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listFulfillmentProfileRequirements(profileId) {
|
||||||
|
const result = await query(
|
||||||
|
`
|
||||||
|
SELECT *
|
||||||
|
FROM fulfillment_profile_requirements
|
||||||
|
WHERE profile_id = $1
|
||||||
|
ORDER BY id ASC
|
||||||
|
`,
|
||||||
|
[Number(profileId)],
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.rows
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function upsertSkuFulfillmentBinding(input) {
|
||||||
|
const existing = await query(
|
||||||
|
`
|
||||||
|
SELECT *
|
||||||
|
FROM sku_fulfillment_bindings
|
||||||
|
WHERE sku_code = $1 AND provider = $2 AND platform = $3 AND shop_id = $4
|
||||||
|
LIMIT 1
|
||||||
|
`,
|
||||||
|
[input.skuCode, input.provider || '', input.platform || '', input.shopId || ''],
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!existing.rows[0]) {
|
||||||
|
const inserted = await query(
|
||||||
|
`
|
||||||
|
INSERT INTO sku_fulfillment_bindings (
|
||||||
|
sku_code,
|
||||||
|
provider,
|
||||||
|
platform,
|
||||||
|
shop_id,
|
||||||
|
profile_id,
|
||||||
|
enabled,
|
||||||
|
priority,
|
||||||
|
config_json,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9, $10)
|
||||||
|
RETURNING *
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
input.skuCode,
|
||||||
|
input.provider || '',
|
||||||
|
input.platform || '',
|
||||||
|
input.shopId || '',
|
||||||
|
Number(input.profileId),
|
||||||
|
input.enabled !== false,
|
||||||
|
Number(input.priority || 100),
|
||||||
|
input.configJson || '{}',
|
||||||
|
input.createdAt,
|
||||||
|
input.updatedAt,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
return inserted.rows[0] || null
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = await query(
|
||||||
|
`
|
||||||
|
UPDATE sku_fulfillment_bindings
|
||||||
|
SET
|
||||||
|
profile_id = $1,
|
||||||
|
enabled = $2,
|
||||||
|
priority = $3,
|
||||||
|
config_json = $4::jsonb,
|
||||||
|
updated_at = $5
|
||||||
|
WHERE id = $6
|
||||||
|
RETURNING *
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
Number(input.profileId),
|
||||||
|
input.enabled !== false,
|
||||||
|
Number(input.priority || 100),
|
||||||
|
input.configJson || '{}',
|
||||||
|
input.updatedAt,
|
||||||
|
Number(existing.rows[0].id),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
return updated.rows[0] || null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveFulfillmentBinding({ skuCode, provider = '', platform = '', shopId = '' }) {
|
||||||
|
const result = await query(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
sfb.*,
|
||||||
|
fp.profile_key,
|
||||||
|
fp.name AS profile_name,
|
||||||
|
fp.executor_key,
|
||||||
|
fp.requires_claim,
|
||||||
|
fp.auto_dispatch,
|
||||||
|
fp.inventory_strategy,
|
||||||
|
fp.config_json AS profile_config_json
|
||||||
|
FROM sku_fulfillment_bindings sfb
|
||||||
|
JOIN fulfillment_profiles fp ON fp.id = sfb.profile_id
|
||||||
|
WHERE sfb.enabled = TRUE
|
||||||
|
AND sfb.sku_code = $1
|
||||||
|
AND (sfb.provider = '' OR sfb.provider = $2)
|
||||||
|
AND (sfb.platform = '' OR sfb.platform = $3)
|
||||||
|
AND (sfb.shop_id = '' OR sfb.shop_id = $4)
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN sfb.shop_id = '' THEN 1 ELSE 0 END,
|
||||||
|
CASE WHEN sfb.platform = '' THEN 1 ELSE 0 END,
|
||||||
|
CASE WHEN sfb.provider = '' THEN 1 ELSE 0 END,
|
||||||
|
sfb.priority ASC,
|
||||||
|
sfb.id ASC
|
||||||
|
LIMIT 1
|
||||||
|
`,
|
||||||
|
[skuCode, provider, platform, shopId],
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.rows[0] || null
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { getDb } from '../db/client.js'
|
import { query } from '../db/client.js'
|
||||||
|
|
||||||
export function createMessageDelivery(input) {
|
export async function createMessageDelivery(input) {
|
||||||
const result = getDb().prepare(`
|
const result = await query(
|
||||||
|
`
|
||||||
INSERT INTO message_deliveries (
|
INSERT INTO message_deliveries (
|
||||||
provider,
|
provider,
|
||||||
platform,
|
platform,
|
||||||
@@ -24,82 +25,93 @@ export function createMessageDelivery(input) {
|
|||||||
sent_at,
|
sent_at,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14::jsonb, $15::jsonb, $16, $17::jsonb, $18, $19, $20, $21)
|
||||||
`).run(
|
RETURNING *
|
||||||
|
`,
|
||||||
|
[
|
||||||
input.provider,
|
input.provider,
|
||||||
input.platform,
|
input.platform,
|
||||||
input.shopId,
|
input.shopId || '',
|
||||||
input.shopName,
|
input.shopName || '',
|
||||||
input.channel,
|
input.channel,
|
||||||
input.orderId,
|
input.orderId || null,
|
||||||
input.taskId,
|
input.taskId || null,
|
||||||
input.platformOrderId,
|
input.platformOrderId || '',
|
||||||
input.recipientKey,
|
input.recipientKey || '',
|
||||||
input.messageContent,
|
input.messageContent || '',
|
||||||
input.claimUrl,
|
input.claimUrl || '',
|
||||||
input.status,
|
input.status || 'pending',
|
||||||
input.requestUrl,
|
input.requestUrl || '',
|
||||||
input.requestHeadersJson,
|
input.requestHeadersJson || '{}',
|
||||||
input.requestBodyJson,
|
input.requestBodyJson || '{}',
|
||||||
input.responseStatus,
|
Number(input.responseStatus || 0),
|
||||||
input.responseJson,
|
input.responseJson || '{}',
|
||||||
input.errorMessage,
|
input.errorMessage || '',
|
||||||
input.sentAt,
|
input.sentAt || null,
|
||||||
input.createdAt,
|
input.createdAt,
|
||||||
input.updatedAt,
|
input.updatedAt,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
return getMessageDeliveryById(Number(result.lastInsertRowid))
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageDeliveryById(deliveryId) {
|
export async function getMessageDeliveryById(deliveryId) {
|
||||||
return getDb().prepare('SELECT * FROM message_deliveries WHERE id = ? LIMIT 1').get(deliveryId) || null
|
const result = await query('SELECT * FROM message_deliveries WHERE id = $1 LIMIT 1', [Number(deliveryId)])
|
||||||
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateMessageDelivery(deliveryId, patch = {}) {
|
export async function updateMessageDelivery(deliveryId, patch = {}) {
|
||||||
const current = getMessageDeliveryById(deliveryId)
|
const current = await getMessageDeliveryById(deliveryId)
|
||||||
|
|
||||||
if (!current) {
|
if (!current) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = { ...current, ...patch }
|
const next = { ...current, ...patch }
|
||||||
|
const result = await query(
|
||||||
getDb().prepare(`
|
`
|
||||||
UPDATE message_deliveries
|
UPDATE message_deliveries
|
||||||
SET
|
SET
|
||||||
status = ?,
|
status = $1,
|
||||||
request_url = ?,
|
request_url = $2,
|
||||||
request_headers_json = ?,
|
request_headers_json = $3::jsonb,
|
||||||
request_body_json = ?,
|
request_body_json = $4::jsonb,
|
||||||
response_status = ?,
|
response_status = $5,
|
||||||
response_json = ?,
|
response_json = $6::jsonb,
|
||||||
error_message = ?,
|
error_message = $7,
|
||||||
sent_at = ?,
|
sent_at = $8,
|
||||||
updated_at = ?
|
updated_at = $9
|
||||||
WHERE id = ?
|
WHERE id = $10
|
||||||
`).run(
|
RETURNING *
|
||||||
|
`,
|
||||||
|
[
|
||||||
next.status,
|
next.status,
|
||||||
next.request_url,
|
next.request_url || '',
|
||||||
next.request_headers_json,
|
next.request_headers_json || '{}',
|
||||||
next.request_body_json,
|
next.request_body_json || '{}',
|
||||||
next.response_status,
|
Number(next.response_status || 0),
|
||||||
next.response_json,
|
next.response_json || '{}',
|
||||||
next.error_message,
|
next.error_message || '',
|
||||||
next.sent_at,
|
next.sent_at || null,
|
||||||
next.updated_at,
|
next.updated_at,
|
||||||
deliveryId,
|
Number(deliveryId),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
return getMessageDeliveryById(deliveryId)
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findLatestSuccessfulMessageDeliveryByTask(taskId, channel) {
|
export async function findLatestSuccessfulMessageDeliveryByTask(taskId, channel) {
|
||||||
return getDb().prepare(`
|
const result = await query(
|
||||||
|
`
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM message_deliveries
|
FROM message_deliveries
|
||||||
WHERE task_id = ? AND channel = ? AND status = 'success'
|
WHERE task_id = $1 AND channel = $2 AND status = 'success'
|
||||||
ORDER BY id DESC
|
ORDER BY id DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`).get(taskId, channel) || null
|
`,
|
||||||
|
[Number(taskId), channel],
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +1,53 @@
|
|||||||
import { getDb } from '../db/client.js'
|
import { query } from '../db/client.js'
|
||||||
|
|
||||||
export function listOrderItemsByOrderId(orderId) {
|
export async function listOrderItemsByOrderId(orderId) {
|
||||||
return getDb().prepare(`
|
const result = await query(
|
||||||
|
`
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM order_items
|
FROM order_items
|
||||||
WHERE order_id = ?
|
WHERE order_id = $1
|
||||||
ORDER BY id ASC
|
ORDER BY id ASC
|
||||||
`).all(orderId)
|
`,
|
||||||
|
[Number(orderId)],
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.rows
|
||||||
}
|
}
|
||||||
|
|
||||||
export function replaceOrderItems(orderId, items) {
|
export async function replaceOrderItems(orderId, items) {
|
||||||
const db = getDb()
|
await query('DELETE FROM order_items WHERE order_id = $1', [Number(orderId)])
|
||||||
const existingItems = listOrderItemsByOrderId(orderId)
|
|
||||||
const updateStatement = db.prepare(`
|
for (const item of items) {
|
||||||
UPDATE order_items
|
await query(
|
||||||
SET
|
`
|
||||||
sku_code = ?,
|
|
||||||
sku_name = ?,
|
|
||||||
quantity = ?,
|
|
||||||
spec_json = ?,
|
|
||||||
delivery_mode = ?,
|
|
||||||
updated_at = ?
|
|
||||||
WHERE id = ?
|
|
||||||
`)
|
|
||||||
const insertStatement = db.prepare(`
|
|
||||||
INSERT INTO order_items (
|
INSERT INTO order_items (
|
||||||
order_id,
|
order_id,
|
||||||
sku_code,
|
sku_code,
|
||||||
sku_name,
|
sku_name,
|
||||||
quantity,
|
quantity,
|
||||||
spec_json,
|
spec_json,
|
||||||
delivery_mode,
|
item_snapshot_json,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES ($1, $2, $3, $4, $5::jsonb, $6::jsonb, $7, $8)
|
||||||
`)
|
`,
|
||||||
|
[
|
||||||
for (let index = 0; index < items.length; index += 1) {
|
Number(orderId),
|
||||||
const item = items[index]
|
|
||||||
const existingItem = existingItems[index] || null
|
|
||||||
|
|
||||||
if (existingItem) {
|
|
||||||
updateStatement.run(
|
|
||||||
item.skuCode,
|
item.skuCode,
|
||||||
item.skuName,
|
item.skuName,
|
||||||
item.quantity,
|
item.quantity,
|
||||||
item.specJson,
|
item.specJson || '{}',
|
||||||
item.deliveryMode,
|
item.itemSnapshotJson || item.specJson || '{}',
|
||||||
item.updatedAt,
|
|
||||||
existingItem.id,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
insertStatement.run(
|
|
||||||
orderId,
|
|
||||||
item.skuCode,
|
|
||||||
item.skuName,
|
|
||||||
item.quantity,
|
|
||||||
item.specJson,
|
|
||||||
item.deliveryMode,
|
|
||||||
item.createdAt,
|
item.createdAt,
|
||||||
item.updatedAt,
|
item.updatedAt,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingItems.length > items.length) {
|
|
||||||
const redundantIds = existingItems.slice(items.length).map((item) => item.id)
|
|
||||||
const placeholders = redundantIds.map(() => '?').join(', ')
|
|
||||||
|
|
||||||
db.prepare(`DELETE FROM order_items WHERE order_id = ? AND id IN (${placeholders})`).run(orderId, ...redundantIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
return listOrderItemsByOrderId(orderId)
|
return listOrderItemsByOrderId(orderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOrderItemById(orderItemId) {
|
export async function getOrderItemById(orderItemId) {
|
||||||
return getDb().prepare('SELECT * FROM order_items WHERE id = ? LIMIT 1').get(orderItemId) || null
|
const result = await query('SELECT * FROM order_items WHERE id = $1 LIMIT 1', [Number(orderItemId)])
|
||||||
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
import { getDb } from '../db/client.js'
|
import { query } from '../db/client.js'
|
||||||
|
|
||||||
export function findOrderByPlatformOrderId({ provider = 'agiso', platform, shopId = '', platformOrderId }) {
|
export async function findOrderByPlatformOrderId({ provider = 'agiso', platform, shopId = '', platformOrderId }) {
|
||||||
return getDb().prepare(`
|
const result = await query(
|
||||||
|
`
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM orders
|
FROM orders
|
||||||
WHERE provider = ? AND platform = ? AND shop_id = ? AND platform_order_id = ?
|
WHERE provider = $1 AND platform = $2 AND shop_id = $3 AND platform_order_id = $4
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`).get(provider, platform, shopId, platformOrderId) || null
|
`,
|
||||||
|
[provider, platform, shopId, platformOrderId],
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createOrder(input) {
|
export async function createOrder(input) {
|
||||||
const result = getDb().prepare(`
|
const result = await query(
|
||||||
|
`
|
||||||
INSERT INTO orders (
|
INSERT INTO orders (
|
||||||
provider,
|
provider,
|
||||||
platform,
|
platform,
|
||||||
@@ -28,8 +34,10 @@ export function createOrder(input) {
|
|||||||
paid_at,
|
paid_at,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13::jsonb, $14, $15, $16)
|
||||||
`).run(
|
RETURNING *
|
||||||
|
`,
|
||||||
|
[
|
||||||
input.provider,
|
input.provider,
|
||||||
input.platform,
|
input.platform,
|
||||||
input.shopId,
|
input.shopId,
|
||||||
@@ -42,35 +50,39 @@ export function createOrder(input) {
|
|||||||
input.receiverContact,
|
input.receiverContact,
|
||||||
input.totalAmount,
|
input.totalAmount,
|
||||||
input.currency,
|
input.currency,
|
||||||
input.rawPayloadJson,
|
input.rawPayloadJson || '{}',
|
||||||
input.paidAt,
|
input.paidAt || null,
|
||||||
input.createdAt,
|
input.createdAt,
|
||||||
input.updatedAt,
|
input.updatedAt,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
return getOrderById(Number(result.lastInsertRowid))
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateOrder(orderId, input) {
|
export async function updateOrder(orderId, input) {
|
||||||
getDb().prepare(`
|
const result = await query(
|
||||||
|
`
|
||||||
UPDATE orders
|
UPDATE orders
|
||||||
SET
|
SET
|
||||||
provider = ?,
|
provider = $1,
|
||||||
platform = ?,
|
platform = $2,
|
||||||
shop_id = ?,
|
shop_id = $3,
|
||||||
shop_name = ?,
|
shop_name = $4,
|
||||||
order_status = ?,
|
order_status = $5,
|
||||||
pay_status = ?,
|
pay_status = $6,
|
||||||
buyer_id = ?,
|
buyer_id = $7,
|
||||||
buyer_name = ?,
|
buyer_name = $8,
|
||||||
receiver_contact = ?,
|
receiver_contact = $9,
|
||||||
total_amount = ?,
|
total_amount = $10,
|
||||||
currency = ?,
|
currency = $11,
|
||||||
raw_payload_json = ?,
|
raw_payload_json = $12::jsonb,
|
||||||
paid_at = ?,
|
paid_at = $13,
|
||||||
updated_at = ?
|
updated_at = $14
|
||||||
WHERE id = ?
|
WHERE id = $15
|
||||||
`).run(
|
RETURNING *
|
||||||
|
`,
|
||||||
|
[
|
||||||
input.provider,
|
input.provider,
|
||||||
input.platform,
|
input.platform,
|
||||||
input.shopId,
|
input.shopId,
|
||||||
@@ -82,20 +94,22 @@ export function updateOrder(orderId, input) {
|
|||||||
input.receiverContact,
|
input.receiverContact,
|
||||||
input.totalAmount,
|
input.totalAmount,
|
||||||
input.currency,
|
input.currency,
|
||||||
input.rawPayloadJson,
|
input.rawPayloadJson || '{}',
|
||||||
input.paidAt,
|
input.paidAt || null,
|
||||||
input.updatedAt,
|
input.updatedAt,
|
||||||
orderId,
|
Number(orderId),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
return getOrderById(orderId)
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOrderById(orderId) {
|
export async function getOrderById(orderId) {
|
||||||
return getDb().prepare('SELECT * FROM orders WHERE id = ? LIMIT 1').get(orderId) || null
|
const result = await query('SELECT * FROM orders WHERE id = $1 LIMIT 1', [Number(orderId)])
|
||||||
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listOrders({
|
export async function listOrders({
|
||||||
page = 1,
|
page = 1,
|
||||||
pageSize = 20,
|
pageSize = 20,
|
||||||
platformOrderId = '',
|
platformOrderId = '',
|
||||||
@@ -109,50 +123,50 @@ export function listOrders({
|
|||||||
const params = []
|
const params = []
|
||||||
|
|
||||||
if (platformOrderId) {
|
if (platformOrderId) {
|
||||||
filters.push('o.platform_order_id LIKE ?')
|
|
||||||
params.push(`%${platformOrderId}%`)
|
params.push(`%${platformOrderId}%`)
|
||||||
|
filters.push(`o.platform_order_id ILIKE $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payStatus) {
|
if (payStatus) {
|
||||||
filters.push('o.pay_status = ?')
|
|
||||||
params.push(payStatus)
|
params.push(payStatus)
|
||||||
|
filters.push(`o.pay_status = $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skuCode) {
|
if (skuCode) {
|
||||||
filters.push('EXISTS (SELECT 1 FROM order_items oi WHERE oi.order_id = o.id AND oi.sku_code = ?)')
|
|
||||||
params.push(skuCode)
|
params.push(skuCode)
|
||||||
|
filters.push(`EXISTS (SELECT 1 FROM order_items oi WHERE oi.order_id = o.id AND oi.sku_code = $${params.length})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dateFrom) {
|
if (dateFrom) {
|
||||||
filters.push('o.created_at >= ?')
|
|
||||||
params.push(dateFrom)
|
params.push(dateFrom)
|
||||||
|
filters.push(`o.created_at >= $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dateTo) {
|
if (dateTo) {
|
||||||
filters.push('o.created_at <= ?')
|
|
||||||
params.push(dateTo)
|
params.push(dateTo)
|
||||||
|
filters.push(`o.created_at <= $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
||||||
const db = getDb()
|
const totalResult = await query(`SELECT COUNT(*)::int AS total FROM orders o ${whereClause}`, params)
|
||||||
const totalRow = db.prepare(`
|
|
||||||
SELECT COUNT(*) AS total
|
|
||||||
FROM orders o
|
|
||||||
${whereClause}
|
|
||||||
`).get(...params)
|
|
||||||
|
|
||||||
const items = db.prepare(`
|
params.push(pageSize)
|
||||||
|
params.push(offset)
|
||||||
|
const itemsResult = await query(
|
||||||
|
`
|
||||||
SELECT
|
SELECT
|
||||||
o.*,
|
o.*,
|
||||||
(SELECT COUNT(*) FROM delivery_tasks dt WHERE dt.order_id = o.id) AS task_count
|
(SELECT COUNT(*)::int FROM fulfillment_tasks ft WHERE ft.order_id = o.id) AS task_count
|
||||||
FROM orders o
|
FROM orders o
|
||||||
${whereClause}
|
${whereClause}
|
||||||
ORDER BY o.id DESC
|
ORDER BY o.id DESC
|
||||||
LIMIT ? OFFSET ?
|
LIMIT $${params.length - 1} OFFSET $${params.length}
|
||||||
`).all(...params, pageSize, offset)
|
`,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items,
|
items: itemsResult.rows,
|
||||||
total: Number(totalRow?.total || 0),
|
total: Number(totalResult.rows[0]?.total || 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,152 +1,227 @@
|
|||||||
import { getDb } from '../db/client.js'
|
import { query, withTransaction } from '../db/client.js'
|
||||||
|
|
||||||
export function listTasksByOrderId(orderId) {
|
const TASK_FIELDS = `
|
||||||
return getDb().prepare(`
|
ft.*,
|
||||||
SELECT *
|
ct.id AS claim_token_id,
|
||||||
FROM delivery_tasks
|
ct.token AS claim_token,
|
||||||
WHERE order_id = ?
|
ct.status AS claim_token_status,
|
||||||
ORDER BY id ASC
|
ct.expired_at AS claim_token_expired_at,
|
||||||
`).all(orderId)
|
ctx.browser_session_id,
|
||||||
|
ctx.login_type,
|
||||||
|
ctx.nickname,
|
||||||
|
ctx.role_id,
|
||||||
|
ctx.role_name,
|
||||||
|
ctx.area,
|
||||||
|
ctx.partition_name,
|
||||||
|
ctx.screenshot_path,
|
||||||
|
ctx.artifacts_json,
|
||||||
|
ctx.state_json,
|
||||||
|
inv.inventory_item_id AS reserved_cdk_id,
|
||||||
|
inv.display_value AS cdk_code,
|
||||||
|
inv.credential_type AS cdk_credential_type,
|
||||||
|
inv.binding_status AS inventory_binding_status
|
||||||
|
`
|
||||||
|
|
||||||
|
const TASK_JOINS = `
|
||||||
|
FROM fulfillment_tasks ft
|
||||||
|
LEFT JOIN claim_tokens ct ON ct.task_id = ft.id
|
||||||
|
LEFT JOIN tencent_browser_contexts ctx ON ctx.task_id = ft.id
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT
|
||||||
|
tib.inventory_item_id,
|
||||||
|
tib.binding_status,
|
||||||
|
ii.display_value,
|
||||||
|
ii.credential_type
|
||||||
|
FROM task_inventory_bindings tib
|
||||||
|
JOIN inventory_items ii ON ii.id = tib.inventory_item_id
|
||||||
|
WHERE tib.task_id = ft.id AND tib.binding_status IN ('reserved', 'consumed')
|
||||||
|
ORDER BY tib.id ASC
|
||||||
|
LIMIT 1
|
||||||
|
) inv ON TRUE
|
||||||
|
`
|
||||||
|
|
||||||
|
function buildTaskSelect(extraFields = '') {
|
||||||
|
const normalizedExtra = String(extraFields || '').trim()
|
||||||
|
const fieldSql = normalizedExtra ? `${TASK_FIELDS}, ${normalizedExtra}` : TASK_FIELDS
|
||||||
|
return `SELECT ${fieldSql} ${TASK_JOINS}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTask(input) {
|
export async function listTasksByOrderId(orderId) {
|
||||||
const result = getDb().prepare(`
|
const result = await query(
|
||||||
INSERT INTO delivery_tasks (
|
`${buildTaskSelect()}
|
||||||
|
WHERE ft.order_id = $1
|
||||||
|
ORDER BY ft.id ASC`,
|
||||||
|
[Number(orderId)],
|
||||||
|
)
|
||||||
|
return result.rows
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createTask(input) {
|
||||||
|
return withTransaction(async (client) => {
|
||||||
|
const taskResult = await client.query(
|
||||||
|
`
|
||||||
|
INSERT INTO fulfillment_tasks (
|
||||||
order_id,
|
order_id,
|
||||||
order_item_id,
|
order_item_id,
|
||||||
platform_order_id,
|
unit_index,
|
||||||
task_no,
|
task_no,
|
||||||
|
provider,
|
||||||
|
platform,
|
||||||
|
shop_id,
|
||||||
|
shop_name,
|
||||||
|
platform_order_id,
|
||||||
|
profile_id,
|
||||||
|
executor_key,
|
||||||
task_status,
|
task_status,
|
||||||
login_type,
|
inventory_status,
|
||||||
claim_token_id,
|
delivery_status,
|
||||||
reserved_cdk_id,
|
|
||||||
browser_session_id,
|
|
||||||
nickname,
|
|
||||||
role_id,
|
|
||||||
role_name,
|
|
||||||
area,
|
|
||||||
partition_name,
|
|
||||||
result_code,
|
result_code,
|
||||||
result_message,
|
result_message,
|
||||||
screenshot_path,
|
claim_token,
|
||||||
artifacts_json,
|
claim_expires_at,
|
||||||
|
automation_mode,
|
||||||
|
requires_claim,
|
||||||
|
user_action_status,
|
||||||
|
attempt_count,
|
||||||
last_error,
|
last_error,
|
||||||
retry_count,
|
context_json,
|
||||||
expires_at,
|
|
||||||
claimed_at,
|
|
||||||
role_confirmed_at,
|
|
||||||
redeemed_at,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES (
|
||||||
`).run(
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10,
|
||||||
|
$11, $12, $13, $14, $15, $16, $17, $18, $19,
|
||||||
|
$20, $21, $22, $23, $24::jsonb, $25, $26
|
||||||
|
)
|
||||||
|
RETURNING id
|
||||||
|
`,
|
||||||
|
[
|
||||||
input.orderId,
|
input.orderId,
|
||||||
input.orderItemId,
|
input.orderItemId,
|
||||||
input.platformOrderId,
|
input.unitIndex,
|
||||||
input.taskNo,
|
input.taskNo,
|
||||||
input.taskStatus,
|
input.provider,
|
||||||
input.loginType,
|
input.platform,
|
||||||
input.claimTokenId,
|
input.shopId || '',
|
||||||
input.reservedCdkId,
|
input.shopName || '',
|
||||||
input.browserSessionId,
|
input.platformOrderId,
|
||||||
input.nickname,
|
input.profileId,
|
||||||
input.roleId,
|
input.executorKey,
|
||||||
input.roleName,
|
input.taskStatus || 'pending_payment',
|
||||||
input.area,
|
input.inventoryStatus || 'pending',
|
||||||
input.partitionName,
|
input.deliveryStatus || 'pending',
|
||||||
input.resultCode,
|
input.resultCode || '',
|
||||||
input.resultMessage,
|
input.resultMessage || '',
|
||||||
input.screenshotPath,
|
input.claimToken || '',
|
||||||
input.artifactsJson,
|
input.claimExpiresAt || null,
|
||||||
input.lastError,
|
input.automationMode || 'manual',
|
||||||
input.retryCount,
|
Boolean(input.requiresClaim),
|
||||||
input.expiresAt,
|
input.userActionStatus || 'not_required',
|
||||||
input.claimedAt,
|
input.attemptCount || 0,
|
||||||
input.roleConfirmedAt,
|
input.lastError || '',
|
||||||
input.redeemedAt,
|
input.contextJson || '{}',
|
||||||
input.createdAt,
|
input.createdAt,
|
||||||
input.updatedAt,
|
input.updatedAt,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
return getTaskById(Number(result.lastInsertRowid))
|
const taskId = Number(taskResult.rows[0]?.id || 0)
|
||||||
|
if (input.tencentContext) {
|
||||||
|
await upsertTencentBrowserContextWithClient(client, taskId, input.tencentContext, input.createdAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateTask(taskId, patch) {
|
return getTaskById(taskId)
|
||||||
const current = getTaskById(taskId)
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateTask(taskId, patch) {
|
||||||
|
const current = await getTaskById(taskId)
|
||||||
if (!current) {
|
if (!current) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return withTransaction(async (client) => {
|
||||||
const next = { ...current, ...patch }
|
const next = { ...current, ...patch }
|
||||||
|
|
||||||
getDb().prepare(`
|
await client.query(
|
||||||
UPDATE delivery_tasks
|
`
|
||||||
|
UPDATE fulfillment_tasks
|
||||||
SET
|
SET
|
||||||
task_status = ?,
|
task_status = $1,
|
||||||
login_type = ?,
|
inventory_status = $2,
|
||||||
claim_token_id = ?,
|
delivery_status = $3,
|
||||||
reserved_cdk_id = ?,
|
result_code = $4,
|
||||||
browser_session_id = ?,
|
result_message = $5,
|
||||||
nickname = ?,
|
claim_token = $6,
|
||||||
role_id = ?,
|
claim_expires_at = $7,
|
||||||
role_name = ?,
|
automation_mode = $8,
|
||||||
area = ?,
|
requires_claim = $9,
|
||||||
partition_name = ?,
|
user_action_status = $10,
|
||||||
result_code = ?,
|
attempt_count = $11,
|
||||||
result_message = ?,
|
last_error = $12,
|
||||||
screenshot_path = ?,
|
context_json = $13::jsonb,
|
||||||
artifacts_json = ?,
|
updated_at = $14
|
||||||
last_error = ?,
|
WHERE id = $15
|
||||||
retry_count = ?,
|
`,
|
||||||
expires_at = ?,
|
[
|
||||||
claimed_at = ?,
|
|
||||||
role_confirmed_at = ?,
|
|
||||||
redeemed_at = ?,
|
|
||||||
updated_at = ?
|
|
||||||
WHERE id = ?
|
|
||||||
`).run(
|
|
||||||
next.task_status,
|
next.task_status,
|
||||||
next.login_type,
|
next.inventory_status,
|
||||||
next.claim_token_id,
|
next.delivery_status,
|
||||||
next.reserved_cdk_id,
|
|
||||||
next.browser_session_id,
|
|
||||||
next.nickname,
|
|
||||||
next.role_id,
|
|
||||||
next.role_name,
|
|
||||||
next.area,
|
|
||||||
next.partition_name,
|
|
||||||
next.result_code,
|
next.result_code,
|
||||||
next.result_message,
|
next.result_message,
|
||||||
next.screenshot_path,
|
next.claim_token || '',
|
||||||
next.artifacts_json,
|
next.claim_expires_at || null,
|
||||||
next.last_error,
|
next.automation_mode || 'manual',
|
||||||
next.retry_count,
|
Boolean(next.requires_claim),
|
||||||
next.expires_at,
|
next.user_action_status || 'not_required',
|
||||||
next.claimed_at,
|
next.attempt_count || 0,
|
||||||
next.role_confirmed_at,
|
next.last_error || '',
|
||||||
next.redeemed_at,
|
next.context_json || '{}',
|
||||||
next.updated_at,
|
next.updated_at,
|
||||||
taskId,
|
Number(taskId),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (containsTencentPatch(patch)) {
|
||||||
|
await upsertTencentBrowserContextWithClient(client, Number(taskId), {
|
||||||
|
browserSessionId: patch.browser_session_id,
|
||||||
|
loginType: patch.login_type,
|
||||||
|
nickname: patch.nickname,
|
||||||
|
roleId: patch.role_id,
|
||||||
|
roleName: patch.role_name,
|
||||||
|
area: patch.area,
|
||||||
|
partitionName: patch.partition_name,
|
||||||
|
screenshotPath: patch.screenshot_path,
|
||||||
|
artifactsJson: patch.artifacts_json,
|
||||||
|
stateJson: patch.state_json,
|
||||||
|
}, patch.updated_at || current.updated_at)
|
||||||
|
}
|
||||||
|
|
||||||
return getTaskById(taskId)
|
return getTaskById(taskId)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTaskById(taskId) {
|
export async function getTaskById(taskId) {
|
||||||
return getDb().prepare('SELECT * FROM delivery_tasks WHERE id = ? LIMIT 1').get(taskId) || null
|
const result = await query(
|
||||||
|
`${buildTaskSelect()}
|
||||||
|
WHERE ft.id = $1
|
||||||
|
LIMIT 1`,
|
||||||
|
[Number(taskId)],
|
||||||
|
)
|
||||||
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findTaskByClaimTokenId(claimTokenId) {
|
export async function findTaskByClaimTokenId(claimTokenId) {
|
||||||
return getDb().prepare(`
|
const result = await query(
|
||||||
SELECT *
|
`${buildTaskSelect()}
|
||||||
FROM delivery_tasks
|
JOIN claim_tokens ctf ON ctf.task_id = ft.id
|
||||||
WHERE claim_token_id = ?
|
WHERE ctf.id = $1
|
||||||
LIMIT 1
|
LIMIT 1`,
|
||||||
`).get(claimTokenId) || null
|
[Number(claimTokenId)],
|
||||||
|
)
|
||||||
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listTasks({
|
export async function listTasks({
|
||||||
page = 1,
|
page = 1,
|
||||||
pageSize = 20,
|
pageSize = 20,
|
||||||
status = '',
|
status = '',
|
||||||
@@ -162,67 +237,173 @@ export function listTasks({
|
|||||||
const params = []
|
const params = []
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
filters.push('dt.task_status = ?')
|
|
||||||
params.push(status)
|
params.push(status)
|
||||||
|
filters.push(`ft.task_status = $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (platformOrderId) {
|
if (platformOrderId) {
|
||||||
filters.push('dt.platform_order_id LIKE ?')
|
|
||||||
params.push(`%${platformOrderId}%`)
|
params.push(`%${platformOrderId}%`)
|
||||||
|
filters.push(`ft.platform_order_id ILIKE $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (taskNo) {
|
if (taskNo) {
|
||||||
filters.push('dt.task_no LIKE ?')
|
|
||||||
params.push(`%${taskNo}%`)
|
params.push(`%${taskNo}%`)
|
||||||
|
filters.push(`ft.task_no ILIKE $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skuCode) {
|
if (skuCode) {
|
||||||
filters.push('oi.sku_code = ?')
|
|
||||||
params.push(skuCode)
|
params.push(skuCode)
|
||||||
|
filters.push(`oi.sku_code = $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roleId) {
|
if (roleId) {
|
||||||
filters.push('dt.role_id LIKE ?')
|
|
||||||
params.push(`%${roleId}%`)
|
params.push(`%${roleId}%`)
|
||||||
|
filters.push(`ctx.role_id ILIKE $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dateFrom) {
|
if (dateFrom) {
|
||||||
filters.push('dt.created_at >= ?')
|
|
||||||
params.push(dateFrom)
|
params.push(dateFrom)
|
||||||
|
filters.push(`ft.created_at >= $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dateTo) {
|
if (dateTo) {
|
||||||
filters.push('dt.created_at <= ?')
|
|
||||||
params.push(dateTo)
|
params.push(dateTo)
|
||||||
|
filters.push(`ft.created_at <= $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
||||||
const db = getDb()
|
const totalResult = await query(
|
||||||
const totalRow = db.prepare(`
|
`
|
||||||
SELECT COUNT(*) AS total
|
SELECT COUNT(*)::int AS total
|
||||||
FROM delivery_tasks dt
|
FROM fulfillment_tasks ft
|
||||||
LEFT JOIN order_items oi ON oi.id = dt.order_item_id
|
LEFT JOIN order_items oi ON oi.id = ft.order_item_id
|
||||||
|
LEFT JOIN tencent_browser_contexts ctx ON ctx.task_id = ft.id
|
||||||
${whereClause}
|
${whereClause}
|
||||||
`).get(...params)
|
`,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
|
||||||
const items = db.prepare(`
|
params.push(pageSize)
|
||||||
SELECT
|
params.push(offset)
|
||||||
dt.*,
|
const itemsResult = await query(
|
||||||
oi.sku_code,
|
`${buildTaskSelect('oi.sku_code, oi.sku_name, oi.quantity')}
|
||||||
oi.sku_name,
|
LEFT JOIN order_items oi ON oi.id = ft.order_item_id
|
||||||
ci.cdk_code,
|
|
||||||
ct.token AS claim_token
|
|
||||||
FROM delivery_tasks dt
|
|
||||||
LEFT JOIN order_items oi ON oi.id = dt.order_item_id
|
|
||||||
LEFT JOIN cdk_inventory ci ON ci.id = dt.reserved_cdk_id
|
|
||||||
LEFT JOIN claim_tokens ct ON ct.id = dt.claim_token_id
|
|
||||||
${whereClause}
|
${whereClause}
|
||||||
ORDER BY dt.id DESC
|
ORDER BY ft.id DESC
|
||||||
LIMIT ? OFFSET ?
|
LIMIT $${params.length - 1} OFFSET $${params.length}`,
|
||||||
`).all(...params, pageSize, offset)
|
params,
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items,
|
items: itemsResult.rows,
|
||||||
total: Number(totalRow?.total || 0),
|
total: Number(totalResult.rows[0]?.total || 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function upsertTencentBrowserContextWithClient(client, taskId, patch = {}, timestamp) {
|
||||||
|
const currentResult = await client.query(
|
||||||
|
'SELECT * FROM tencent_browser_contexts WHERE task_id = $1 LIMIT 1',
|
||||||
|
[Number(taskId)],
|
||||||
|
)
|
||||||
|
const current = currentResult.rows[0] || null
|
||||||
|
|
||||||
|
const next = {
|
||||||
|
browser_session_id: patch.browserSessionId ?? current?.browser_session_id ?? '',
|
||||||
|
login_type: patch.loginType ?? current?.login_type ?? '',
|
||||||
|
nickname: patch.nickname ?? current?.nickname ?? '',
|
||||||
|
role_id: patch.roleId ?? current?.role_id ?? '',
|
||||||
|
role_name: patch.roleName ?? current?.role_name ?? '',
|
||||||
|
area: patch.area ?? current?.area ?? '',
|
||||||
|
partition_name: patch.partitionName ?? current?.partition_name ?? '',
|
||||||
|
screenshot_path: patch.screenshotPath ?? current?.screenshot_path ?? '',
|
||||||
|
artifacts_json: patch.artifactsJson ?? current?.artifacts_json ?? '{}',
|
||||||
|
state_json: patch.stateJson ?? current?.state_json ?? '{}',
|
||||||
|
updated_at: timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!current) {
|
||||||
|
await client.query(
|
||||||
|
`
|
||||||
|
INSERT INTO tencent_browser_contexts (
|
||||||
|
task_id,
|
||||||
|
browser_session_id,
|
||||||
|
login_type,
|
||||||
|
nickname,
|
||||||
|
role_id,
|
||||||
|
role_name,
|
||||||
|
area,
|
||||||
|
partition_name,
|
||||||
|
screenshot_path,
|
||||||
|
artifacts_json,
|
||||||
|
state_json,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11::jsonb, $12, $13)
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
Number(taskId),
|
||||||
|
next.browser_session_id,
|
||||||
|
next.login_type,
|
||||||
|
next.nickname,
|
||||||
|
next.role_id,
|
||||||
|
next.role_name,
|
||||||
|
next.area,
|
||||||
|
next.partition_name,
|
||||||
|
next.screenshot_path,
|
||||||
|
typeof next.artifacts_json === 'string' ? next.artifacts_json : JSON.stringify(next.artifacts_json || {}),
|
||||||
|
typeof next.state_json === 'string' ? next.state_json : JSON.stringify(next.state_json || {}),
|
||||||
|
timestamp,
|
||||||
|
timestamp,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.query(
|
||||||
|
`
|
||||||
|
UPDATE tencent_browser_contexts
|
||||||
|
SET
|
||||||
|
browser_session_id = $1,
|
||||||
|
login_type = $2,
|
||||||
|
nickname = $3,
|
||||||
|
role_id = $4,
|
||||||
|
role_name = $5,
|
||||||
|
area = $6,
|
||||||
|
partition_name = $7,
|
||||||
|
screenshot_path = $8,
|
||||||
|
artifacts_json = $9::jsonb,
|
||||||
|
state_json = $10::jsonb,
|
||||||
|
updated_at = $11
|
||||||
|
WHERE task_id = $12
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
next.browser_session_id,
|
||||||
|
next.login_type,
|
||||||
|
next.nickname,
|
||||||
|
next.role_id,
|
||||||
|
next.role_name,
|
||||||
|
next.area,
|
||||||
|
next.partition_name,
|
||||||
|
next.screenshot_path,
|
||||||
|
typeof next.artifacts_json === 'string' ? next.artifacts_json : JSON.stringify(next.artifacts_json || {}),
|
||||||
|
typeof next.state_json === 'string' ? next.state_json : JSON.stringify(next.state_json || {}),
|
||||||
|
timestamp,
|
||||||
|
Number(taskId),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function containsTencentPatch(patch = {}) {
|
||||||
|
return [
|
||||||
|
'browser_session_id',
|
||||||
|
'login_type',
|
||||||
|
'nickname',
|
||||||
|
'role_id',
|
||||||
|
'role_name',
|
||||||
|
'area',
|
||||||
|
'partition_name',
|
||||||
|
'screenshot_path',
|
||||||
|
'artifacts_json',
|
||||||
|
'state_json',
|
||||||
|
].some((key) => Object.prototype.hasOwnProperty.call(patch, key))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { getDb } from '../db/client.js'
|
import { query } from '../db/client.js'
|
||||||
|
|
||||||
export function createWebhookEvent(input) {
|
export async function createWebhookEvent(input) {
|
||||||
const result = getDb().prepare(`
|
const result = await query(
|
||||||
|
`
|
||||||
INSERT INTO webhook_events (
|
INSERT INTO webhook_events (
|
||||||
provider,
|
provider,
|
||||||
platform,
|
platform,
|
||||||
@@ -17,53 +18,59 @@ export function createWebhookEvent(input) {
|
|||||||
process_error,
|
process_error,
|
||||||
related_order_id,
|
related_order_id,
|
||||||
created_at
|
created_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9::jsonb, $10::jsonb, $11, $12, $13, $14)
|
||||||
`).run(
|
RETURNING *
|
||||||
|
`,
|
||||||
|
[
|
||||||
input.provider,
|
input.provider,
|
||||||
input.platform,
|
input.platform,
|
||||||
input.shopId,
|
input.shopId || '',
|
||||||
input.shopName,
|
input.shopName || '',
|
||||||
input.eventType,
|
input.eventType,
|
||||||
input.eventKey,
|
input.eventKey,
|
||||||
input.signatureValid ? 1 : 0,
|
Boolean(input.signatureValid),
|
||||||
input.headersJson,
|
input.headersJson || '{}',
|
||||||
input.queryJson,
|
input.queryJson || '{}',
|
||||||
input.bodyJson,
|
input.bodyJson || '{}',
|
||||||
input.processed ? 1 : 0,
|
Boolean(input.processed),
|
||||||
input.processError,
|
input.processError || '',
|
||||||
input.relatedOrderId,
|
input.relatedOrderId || null,
|
||||||
input.createdAt,
|
input.createdAt,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
return getWebhookEventById(Number(result.lastInsertRowid))
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateWebhookEvent(eventId, patch) {
|
export async function updateWebhookEvent(eventId, patch) {
|
||||||
const current = getWebhookEventById(eventId)
|
const current = await getWebhookEventById(eventId)
|
||||||
|
|
||||||
if (!current) {
|
if (!current) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = { ...current, ...patch }
|
const next = { ...current, ...patch }
|
||||||
|
const result = await query(
|
||||||
getDb().prepare(`
|
`
|
||||||
UPDATE webhook_events
|
UPDATE webhook_events
|
||||||
SET
|
SET
|
||||||
processed = ?,
|
processed = $1,
|
||||||
process_error = ?,
|
process_error = $2,
|
||||||
related_order_id = ?
|
related_order_id = $3
|
||||||
WHERE id = ?
|
WHERE id = $4
|
||||||
`).run(next.processed ? 1 : 0, next.process_error, next.related_order_id, eventId)
|
RETURNING *
|
||||||
|
`,
|
||||||
|
[Boolean(next.processed), next.process_error || '', next.related_order_id || null, Number(eventId)],
|
||||||
|
)
|
||||||
|
|
||||||
return getWebhookEventById(eventId)
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWebhookEventById(eventId) {
|
export async function getWebhookEventById(eventId) {
|
||||||
return getDb().prepare('SELECT * FROM webhook_events WHERE id = ? LIMIT 1').get(eventId) || null
|
const result = await query('SELECT * FROM webhook_events WHERE id = $1 LIMIT 1', [Number(eventId)])
|
||||||
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listWebhookEvents({
|
export async function listWebhookEvents({
|
||||||
page = 1,
|
page = 1,
|
||||||
pageSize = 20,
|
pageSize = 20,
|
||||||
provider = '',
|
provider = '',
|
||||||
@@ -78,62 +85,67 @@ export function listWebhookEvents({
|
|||||||
const params = []
|
const params = []
|
||||||
|
|
||||||
if (provider) {
|
if (provider) {
|
||||||
filters.push('provider = ?')
|
|
||||||
params.push(provider)
|
params.push(provider)
|
||||||
|
filters.push(`provider = $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (platform) {
|
if (platform) {
|
||||||
filters.push('platform = ?')
|
|
||||||
params.push(platform)
|
params.push(platform)
|
||||||
|
filters.push(`platform = $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (processed === '0' || processed === '1') {
|
if (processed === '0' || processed === '1') {
|
||||||
filters.push('processed = ?')
|
params.push(processed === '1')
|
||||||
params.push(Number(processed))
|
filters.push(`processed = $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relatedOrderId) {
|
if (relatedOrderId) {
|
||||||
filters.push('related_order_id = ?')
|
|
||||||
params.push(Number(relatedOrderId))
|
params.push(Number(relatedOrderId))
|
||||||
|
filters.push(`related_order_id = $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dateFrom) {
|
if (dateFrom) {
|
||||||
filters.push('created_at >= ?')
|
|
||||||
params.push(dateFrom)
|
params.push(dateFrom)
|
||||||
|
filters.push(`created_at >= $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dateTo) {
|
if (dateTo) {
|
||||||
filters.push('created_at <= ?')
|
|
||||||
params.push(dateTo)
|
params.push(dateTo)
|
||||||
|
filters.push(`created_at <= $${params.length}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
||||||
const db = getDb()
|
const totalResult = await query(`SELECT COUNT(*)::int AS total FROM webhook_events ${whereClause}`, params)
|
||||||
const totalRow = db.prepare(`
|
|
||||||
SELECT COUNT(*) AS total
|
|
||||||
FROM webhook_events
|
|
||||||
${whereClause}
|
|
||||||
`).get(...params)
|
|
||||||
|
|
||||||
const items = db.prepare(`
|
params.push(pageSize)
|
||||||
|
params.push(offset)
|
||||||
|
const itemsResult = await query(
|
||||||
|
`
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM webhook_events
|
FROM webhook_events
|
||||||
${whereClause}
|
${whereClause}
|
||||||
ORDER BY id DESC
|
ORDER BY id DESC
|
||||||
LIMIT ? OFFSET ?
|
LIMIT $${params.length - 1} OFFSET $${params.length}
|
||||||
`).all(...params, pageSize, offset)
|
`,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items,
|
items: itemsResult.rows,
|
||||||
total: Number(totalRow?.total || 0),
|
total: Number(totalResult.rows[0]?.total || 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listWebhookEventsByOrderId(orderId) {
|
export async function listWebhookEventsByOrderId(orderId) {
|
||||||
return getDb().prepare(`
|
const result = await query(
|
||||||
|
`
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM webhook_events
|
FROM webhook_events
|
||||||
WHERE related_order_id = ?
|
WHERE related_order_id = $1
|
||||||
ORDER BY id DESC
|
ORDER BY id DESC
|
||||||
`).all(orderId)
|
`,
|
||||||
|
[Number(orderId)],
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.rows
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export async function ensureAdminUsersBootstrapped() {
|
|||||||
const configuredUsers = Array.isArray(runtimeConfig.admin?.defaultUsers) ? runtimeConfig.admin.defaultUsers : []
|
const configuredUsers = Array.isArray(runtimeConfig.admin?.defaultUsers) ? runtimeConfig.admin.defaultUsers : []
|
||||||
|
|
||||||
for (const configuredUser of configuredUsers) {
|
for (const configuredUser of configuredUsers) {
|
||||||
const username = String(configuredUser?.username || '').trim()
|
const username = String(configuredUser?.username || '').trim().toLowerCase()
|
||||||
const password = String(configuredUser?.password || '').trim()
|
const password = String(configuredUser?.password || '').trim()
|
||||||
const role = normalizeAdminRole(configuredUser?.role)
|
const role = normalizeAdminRole(configuredUser?.role)
|
||||||
|
|
||||||
@@ -26,12 +26,12 @@ export async function ensureAdminUsersBootstrapped() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getAdminUserByUsername(username)) {
|
if (await getAdminUserByUsername(username)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = nowIso()
|
const now = nowIso()
|
||||||
createAdminUser({
|
await createAdminUser({
|
||||||
username,
|
username,
|
||||||
passwordHash: hashAdminPassword(password),
|
passwordHash: hashAdminPassword(password),
|
||||||
role,
|
role,
|
||||||
@@ -42,10 +42,10 @@ export async function ensureAdminUsersBootstrapped() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loginAdmin(username, password) {
|
export async function loginAdmin(username, password) {
|
||||||
ensureAdminAuthConfigured()
|
ensureAdminAuthConfigured()
|
||||||
|
|
||||||
const normalizedUsername = String(username || '').trim()
|
const normalizedUsername = String(username || '').trim().toLowerCase()
|
||||||
const normalizedPassword = String(password || '').trim()
|
const normalizedPassword = String(password || '').trim()
|
||||||
|
|
||||||
if (!normalizedUsername || !normalizedPassword) {
|
if (!normalizedUsername || !normalizedPassword) {
|
||||||
@@ -55,7 +55,7 @@ export function loginAdmin(username, password) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = getAdminUserByUsername(normalizedUsername)
|
const user = await getAdminUserByUsername(normalizedUsername)
|
||||||
if (!user || user.status !== 'active' || !verifyAdminPassword(normalizedPassword, user.password_hash)) {
|
if (!user || user.status !== 'active' || !verifyAdminPassword(normalizedPassword, user.password_hash)) {
|
||||||
throw createHttpError('账号或密码错误', {
|
throw createHttpError('账号或密码错误', {
|
||||||
statusCode: 401,
|
statusCode: 401,
|
||||||
@@ -66,7 +66,7 @@ export function loginAdmin(username, password) {
|
|||||||
return createAdminSession(user)
|
return createAdminSession(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verifyAdminSessionToken(token) {
|
export async function verifyAdminSessionToken(token) {
|
||||||
ensureAdminAuthConfigured()
|
ensureAdminAuthConfigured()
|
||||||
|
|
||||||
const normalizedToken = String(token || '').trim()
|
const normalizedToken = String(token || '').trim()
|
||||||
@@ -113,7 +113,7 @@ export function verifyAdminSessionToken(token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const userId = Number(payload?.uid || 0)
|
const userId = Number(payload?.uid || 0)
|
||||||
const user = getAdminUserById(userId)
|
const user = await getAdminUserById(userId)
|
||||||
if (!user || user.status !== 'active') {
|
if (!user || user.status !== 'active') {
|
||||||
throw createHttpError('后台账号已不可用,请重新登录', {
|
throw createHttpError('后台账号已不可用,请重新登录', {
|
||||||
statusCode: 401,
|
statusCode: 401,
|
||||||
@@ -122,16 +122,16 @@ export function verifyAdminSessionToken(token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sessionId: String(payload.sid || '').trim(),
|
sessionId: String(payload?.sid || '').trim(),
|
||||||
userId: user.id,
|
userId: Number(user.id),
|
||||||
username: user.username,
|
username: String(user.username || ''),
|
||||||
role: normalizeAdminRole(user.role),
|
role: normalizeAdminRole(user.role),
|
||||||
expiresAt,
|
expiresAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAdminSessionSummary(token) {
|
export async function getAdminSessionSummary(token) {
|
||||||
const session = verifyAdminSessionToken(token)
|
const session = await verifyAdminSessionToken(token)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
@@ -155,10 +155,10 @@ export function requireAdminRole(session, allowedRoles) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAdminUserList(query = {}) {
|
export async function getAdminUserList(query = {}) {
|
||||||
const page = normalizePage(query.page)
|
const page = normalizePage(query.page)
|
||||||
const pageSize = normalizePageSize(query.pageSize)
|
const pageSize = normalizePageSize(query.pageSize)
|
||||||
const { items, total } = listAdminUsers({
|
const { items, total } = await listAdminUsers({
|
||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
username: String(query.username || '').trim(),
|
username: String(query.username || '').trim(),
|
||||||
@@ -172,7 +172,7 @@ export function getAdminUserList(query = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createManagedAdminUser(payload = {}) {
|
export async function createManagedAdminUser(payload = {}) {
|
||||||
const username = normalizeUsername(payload.username)
|
const username = normalizeUsername(payload.username)
|
||||||
const password = normalizePassword(payload.password)
|
const password = normalizePassword(payload.password)
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ export function createManagedAdminUser(payload = {}) {
|
|||||||
validateUsername(username)
|
validateUsername(username)
|
||||||
validatePassword(password)
|
validatePassword(password)
|
||||||
|
|
||||||
if (getAdminUserByUsername(username)) {
|
if (await getAdminUserByUsername(username)) {
|
||||||
throw createHttpError('后台账号已存在', {
|
throw createHttpError('后台账号已存在', {
|
||||||
statusCode: 409,
|
statusCode: 409,
|
||||||
errorCode: 'admin_user_exists',
|
errorCode: 'admin_user_exists',
|
||||||
@@ -201,7 +201,7 @@ export function createManagedAdminUser(payload = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const now = nowIso()
|
const now = nowIso()
|
||||||
const created = createAdminUser({
|
const created = await createAdminUser({
|
||||||
username,
|
username,
|
||||||
passwordHash: hashAdminPassword(password),
|
passwordHash: hashAdminPassword(password),
|
||||||
role: normalizeAdminRole(payload.role),
|
role: normalizeAdminRole(payload.role),
|
||||||
@@ -215,8 +215,8 @@ export function createManagedAdminUser(payload = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateManagedAdminUserRole(userId, payload = {}, session) {
|
export async function updateManagedAdminUserRole(userId, payload = {}, session) {
|
||||||
const user = getRequiredAdminUser(userId)
|
const user = await getRequiredAdminUser(userId)
|
||||||
const role = normalizeAdminRole(payload.role)
|
const role = normalizeAdminRole(payload.role)
|
||||||
|
|
||||||
if (user.role === role) {
|
if (user.role === role) {
|
||||||
@@ -225,8 +225,8 @@ export function updateManagedAdminUserRole(userId, payload = {}, session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureAdminUserChangeAllowed(user, { nextRole: role }, session)
|
await ensureAdminUserChangeAllowed(user, { nextRole: role }, session)
|
||||||
const updated = updateAdminUser(user.id, {
|
const updated = await updateAdminUser(user.id, {
|
||||||
role,
|
role,
|
||||||
updated_at: nowIso(),
|
updated_at: nowIso(),
|
||||||
})
|
})
|
||||||
@@ -236,8 +236,8 @@ export function updateManagedAdminUserRole(userId, payload = {}, session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateManagedAdminUserStatus(userId, payload = {}, session) {
|
export async function updateManagedAdminUserStatus(userId, payload = {}, session) {
|
||||||
const user = getRequiredAdminUser(userId)
|
const user = await getRequiredAdminUser(userId)
|
||||||
const status = normalizeAdminUserStatus(payload.status)
|
const status = normalizeAdminUserStatus(payload.status)
|
||||||
|
|
||||||
if (user.status === status) {
|
if (user.status === status) {
|
||||||
@@ -246,8 +246,8 @@ export function updateManagedAdminUserStatus(userId, payload = {}, session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureAdminUserChangeAllowed(user, { nextStatus: status }, session)
|
await ensureAdminUserChangeAllowed(user, { nextStatus: status }, session)
|
||||||
const updated = updateAdminUser(user.id, {
|
const updated = await updateAdminUser(user.id, {
|
||||||
status,
|
status,
|
||||||
updated_at: nowIso(),
|
updated_at: nowIso(),
|
||||||
})
|
})
|
||||||
@@ -257,8 +257,8 @@ export function updateManagedAdminUserStatus(userId, payload = {}, session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetManagedAdminUserPassword(userId, payload = {}) {
|
export async function resetManagedAdminUserPassword(userId, payload = {}) {
|
||||||
const user = getRequiredAdminUser(userId)
|
const user = await getRequiredAdminUser(userId)
|
||||||
const password = normalizePassword(payload.password)
|
const password = normalizePassword(payload.password)
|
||||||
|
|
||||||
if (!password) {
|
if (!password) {
|
||||||
@@ -269,7 +269,7 @@ export function resetManagedAdminUserPassword(userId, payload = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validatePassword(password)
|
validatePassword(password)
|
||||||
const updated = updateAdminUser(user.id, {
|
const updated = await updateAdminUser(user.id, {
|
||||||
password_hash: hashAdminPassword(password),
|
password_hash: hashAdminPassword(password),
|
||||||
updated_at: nowIso(),
|
updated_at: nowIso(),
|
||||||
})
|
})
|
||||||
@@ -298,8 +298,8 @@ function createAdminSession(user) {
|
|||||||
const expiresAt = addHours(issuedAt, Number(runtimeConfig.admin.sessionTtlHours || 12))
|
const expiresAt = addHours(issuedAt, Number(runtimeConfig.admin.sessionTtlHours || 12))
|
||||||
const payload = {
|
const payload = {
|
||||||
sid: crypto.randomBytes(12).toString('hex'),
|
sid: crypto.randomBytes(12).toString('hex'),
|
||||||
uid: user.id,
|
uid: Number(user.id),
|
||||||
usr: user.username,
|
usr: String(user.username || ''),
|
||||||
role: normalizeAdminRole(user.role),
|
role: normalizeAdminRole(user.role),
|
||||||
iat: issuedAt,
|
iat: issuedAt,
|
||||||
exp: expiresAt,
|
exp: expiresAt,
|
||||||
@@ -311,8 +311,8 @@ function createAdminSession(user) {
|
|||||||
token: `${encodedPayload}.${signature}`,
|
token: `${encodedPayload}.${signature}`,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
user: {
|
user: {
|
||||||
userId: user.id,
|
userId: Number(user.id),
|
||||||
username: user.username,
|
username: String(user.username || ''),
|
||||||
role: normalizeAdminRole(user.role),
|
role: normalizeAdminRole(user.role),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -409,8 +409,8 @@ function validatePassword(password) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRequiredAdminUser(userId) {
|
async function getRequiredAdminUser(userId) {
|
||||||
const user = getAdminUserById(Number(userId))
|
const user = await getAdminUserById(Number(userId))
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw createHttpError('后台用户不存在', {
|
throw createHttpError('后台用户不存在', {
|
||||||
@@ -422,18 +422,18 @@ function getRequiredAdminUser(userId) {
|
|||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureAdminUserChangeAllowed(user, options = {}, session) {
|
async function ensureAdminUserChangeAllowed(user, options = {}, session) {
|
||||||
const nextRole = options.nextRole || user.role
|
const nextRole = options.nextRole || user.role
|
||||||
const nextStatus = options.nextStatus || user.status
|
const nextStatus = options.nextStatus || user.status
|
||||||
|
|
||||||
if (session?.userId === user.id && (nextRole !== 'admin' || nextStatus !== 'active')) {
|
if (session?.userId === Number(user.id) && (nextRole !== 'admin' || nextStatus !== 'active')) {
|
||||||
throw createHttpError('不能停用或降级当前登录账号', {
|
throw createHttpError('不能停用或降级当前登录账号', {
|
||||||
statusCode: 409,
|
statusCode: 409,
|
||||||
errorCode: 'admin_user_self_change_not_allowed',
|
errorCode: 'admin_user_self_change_not_allowed',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.role === 'admin' && (nextRole !== 'admin' || nextStatus !== 'active') && countActiveAdminUsers() <= 1) {
|
if (user.role === 'admin' && (nextRole !== 'admin' || nextStatus !== 'active') && await countActiveAdminUsers() <= 1) {
|
||||||
throw createHttpError('至少保留一个启用中的管理员账号', {
|
throw createHttpError('至少保留一个启用中的管理员账号', {
|
||||||
statusCode: 409,
|
statusCode: 409,
|
||||||
errorCode: 'admin_user_last_admin_not_allowed',
|
errorCode: 'admin_user_last_admin_not_allowed',
|
||||||
@@ -443,8 +443,8 @@ function ensureAdminUserChangeAllowed(user, options = {}, session) {
|
|||||||
|
|
||||||
function mapAdminUser(user) {
|
function mapAdminUser(user) {
|
||||||
return {
|
return {
|
||||||
userId: user.id,
|
userId: Number(user.id),
|
||||||
username: user.username,
|
username: String(user.username || ''),
|
||||||
role: normalizeAdminRole(user.role),
|
role: normalizeAdminRole(user.role),
|
||||||
status: normalizeAdminUserStatus(user.status),
|
status: normalizeAdminUserStatus(user.status),
|
||||||
createdAt: user.created_at,
|
createdAt: user.created_at,
|
||||||
|
|||||||
@@ -127,6 +127,10 @@ function defaultErrorCodeForStatus(statusCode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizeResponseDateTime(value) {
|
function normalizeResponseDateTime(value) {
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return formatResponseDateTime(value.toISOString())
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.map((item) => normalizeResponseDateTime(item))
|
return value.map((item) => normalizeResponseDateTime(item))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { existsSync } from 'node:fs'
|
|
||||||
import fs from 'node:fs/promises'
|
import fs from 'node:fs/promises'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import process from 'node:process'
|
import process from 'node:process'
|
||||||
@@ -90,17 +89,8 @@ function resolveConsoleMethod(level) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveDataRoot() {
|
function resolveDataRoot() {
|
||||||
const configuredDatabasePath = String(runtimeConfig.database?.filePath || '').trim()
|
const configured = String(runtimeConfig.data?.root || '').trim()
|
||||||
|
return configured ? path.resolve(configured) : path.resolve(PROJECT_ROOT, 'data')
|
||||||
if (configuredDatabasePath) {
|
|
||||||
const configuredDataRoot = path.dirname(configuredDatabasePath)
|
|
||||||
|
|
||||||
if (existsSync(configuredDataRoot)) {
|
|
||||||
return configuredDataRoot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return path.resolve(PROJECT_ROOT, 'data')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeLogValue(value) {
|
function normalizeLogValue(value) {
|
||||||
|
|||||||
Reference in New Issue
Block a user