Compare commits
340
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
633ba59775 | ||
|
|
e281ff853b | ||
|
|
f885f0b907 | ||
|
|
e4f93f7193 | ||
|
|
0e3424c4a7 | ||
|
|
c51255d407 | ||
|
|
4c210723cd | ||
|
|
f059674852 | ||
|
|
247fee6d6b | ||
|
|
457319e627 | ||
|
|
69e3d7368d | ||
|
|
bd920f76b0 | ||
|
|
442dda711d | ||
|
|
1e05a643e5 | ||
|
|
839dba8d9b | ||
|
|
15a1f9d8ed | ||
|
|
ca1fc2847f | ||
|
|
64bfb9ad3d | ||
|
|
798e85c930 | ||
|
|
458ddd97df | ||
|
|
1a7a7245a6 | ||
|
|
65082959c0 | ||
|
|
5925cf493b | ||
|
|
aaf96db2d0 | ||
|
|
857171ce9d | ||
|
|
41962e7a6e | ||
|
|
0ec386e674 | ||
|
|
c88d172af6 | ||
|
|
bb435fb0ac | ||
|
|
fb39c8b606 | ||
|
|
48d0363920 | ||
|
|
bc18c7ff12 | ||
|
|
dada0d978b | ||
|
|
fe3c6e5b58 | ||
|
|
7b8a613afa | ||
|
|
4ff6ed0786 | ||
|
|
55915ed826 | ||
|
|
b0016cfd8d | ||
|
|
dc201bf92e | ||
|
|
a50aafb1ec | ||
|
|
19bcca812b | ||
|
|
068b8047dd | ||
|
|
fe5b25a7e4 | ||
|
|
7cb5dd376e | ||
|
|
2dff1be750 | ||
|
|
ad89e8fed7 | ||
|
|
9b8196ecab | ||
|
|
53d3668306 | ||
|
|
cd824999f3 | ||
|
|
9e34757d6f | ||
|
|
1f2dfb5caf | ||
|
|
12668cee87 | ||
|
|
9a15d28da5 | ||
|
|
fd373d9708 | ||
|
|
b0deef5e6e | ||
|
|
119d57772e | ||
|
|
1a5063a5d9 | ||
|
|
67774509d9 | ||
|
|
4d526b83ab | ||
|
|
5328bb09bf | ||
|
|
c68ac7a3db | ||
|
|
9ac41e9308 | ||
|
|
3834d67a30 | ||
|
|
226527f1ce | ||
|
|
ad27acad7e | ||
|
|
3af145abe5 | ||
|
|
fc4a6a7afc | ||
|
|
d85ff85999 | ||
|
|
2744690f9f | ||
|
|
c3bf8af3e6 | ||
|
|
0b84370270 | ||
|
|
171c7d38e8 | ||
|
|
a70e9e2c07 | ||
|
|
4d4c1f39d4 | ||
|
|
5bd347e010 | ||
|
|
088d7aaacb | ||
|
|
33fa3709d9 | ||
|
|
ec721bf95a | ||
|
|
070a951e7c | ||
|
|
01cffa4758 | ||
|
|
d6edb88b76 | ||
|
|
a280b4bb22 | ||
|
|
898471423f | ||
|
|
444c65288c | ||
|
|
5234812990 | ||
|
|
c769b60aaa | ||
|
|
b0bdb608c1 | ||
|
|
b7ae1aa41a | ||
|
|
8170f727a3 | ||
|
|
4a17df78b8 | ||
|
|
2a181777ad | ||
|
|
b1e297245c | ||
|
|
b5005f21d5 | ||
|
|
394949f9f6 | ||
|
|
76e7c8c401 | ||
|
|
d3ceeb1e16 | ||
|
|
9292352be1 | ||
|
|
761c123b65 | ||
|
|
c6f11014d6 | ||
|
|
03f72c87de | ||
|
|
2b5256d4a1 | ||
|
|
90345fba22 | ||
|
|
3c6f1beed1 | ||
|
|
fcf6d3e6b4 | ||
|
|
487b5282a6 | ||
|
|
0176aa6952 | ||
|
|
52028b9bbd | ||
|
|
57192b7586 | ||
|
|
f4560479d3 | ||
|
|
99fb346566 | ||
|
|
633e7a7055 | ||
|
|
229a0b28fd | ||
|
|
6708f1f7c5 | ||
|
|
53073461e9 | ||
|
|
0b63bcd74e | ||
|
|
26ef67fb6a | ||
|
|
14f993c1f8 | ||
|
|
67b760a599 | ||
|
|
485af688d2 | ||
|
|
e9c73333b3 | ||
|
|
0757a699ff | ||
|
|
b9a71fe0b9 | ||
|
|
c4f36fc50d | ||
|
|
482f7ac1ae | ||
|
|
79f5d3f215 | ||
|
|
2216d00d51 | ||
|
|
0cd09441da | ||
|
|
6ccc102830 | ||
|
|
1ef4380422 | ||
|
|
d30d9ea4d0 | ||
|
|
b78faa1aa2 | ||
|
|
96e475d60d | ||
|
|
433b2ee56f | ||
|
|
67fee21616 | ||
|
|
fb02cbcf39 | ||
|
|
4c70978da4 | ||
|
|
16135e5a3e | ||
|
|
4994841709 | ||
|
|
1d8d6701a6 | ||
|
|
f350bf5ef3 | ||
|
|
dc358798e9 | ||
|
|
0cd353450a | ||
|
|
e64b5e23fe | ||
|
|
2ecb24cf8d | ||
|
|
827d8921a8 | ||
|
|
0eb27e4ac0 | ||
|
|
55aa054005 | ||
|
|
232d1c22a3 | ||
|
|
e0c8f82bcf | ||
|
|
35de17a2d4 | ||
|
|
608662a054 | ||
|
|
78b839f468 | ||
|
|
482d332f49 | ||
|
|
7804f64ced | ||
|
|
6add563ee8 | ||
|
|
4724b9db6a | ||
|
|
44352aeb2f | ||
|
|
8ad8e61793 | ||
|
|
57b58f1c40 | ||
|
|
530a65de80 | ||
|
|
3357ace7e1 | ||
|
|
94e997ec4d | ||
|
|
ecc3d7a504 | ||
|
|
37ffce40b2 | ||
|
|
4b9127e1ab | ||
|
|
461a65f810 | ||
|
|
ca4f591a13 | ||
|
|
0af671b4ed | ||
|
|
c0a4317a7e | ||
|
|
500f43f673 | ||
|
|
751c03d421 | ||
|
|
fbacb74544 | ||
|
|
b8560372cc | ||
|
|
87c5b1eccc | ||
|
|
95c052e9ea | ||
|
|
d8e5319647 | ||
|
|
99c8c7c68b | ||
|
|
352a6293b4 | ||
|
|
7c1a0287a0 | ||
|
|
df70b336a0 | ||
|
|
9891ffee23 | ||
|
|
94aeacd3a2 | ||
|
|
04a497c791 | ||
|
|
e0f6eed0d4 | ||
|
|
04f78695ed | ||
|
|
0857de09d8 | ||
|
|
059b38e8fe | ||
|
|
2ce682c3fc | ||
|
|
b461532075 | ||
|
|
54617c927e | ||
|
|
5018167696 | ||
|
|
53736c96e8 | ||
|
|
c78ed1cc32 | ||
|
|
e12bdf010e | ||
|
|
91f04f5288 | ||
|
|
2ee39056eb | ||
|
|
ca12f14b00 | ||
|
|
3997349c21 | ||
|
|
0bfeb0839e | ||
|
|
4f4fa5fcb7 | ||
|
|
39d2cc5909 | ||
|
|
afd3c96070 | ||
|
|
2f781ebb8a | ||
|
|
b29576fcd1 | ||
|
|
f186aee00b | ||
|
|
e28288f691 | ||
|
|
344003e034 | ||
|
|
55019443c0 | ||
|
|
66bc230d7e | ||
|
|
a09036c67b | ||
|
|
cd085d2712 | ||
|
|
0f223f7f91 | ||
|
|
ef957f7daa | ||
|
|
f02c33cbb7 | ||
|
|
80fb5a8f53 | ||
|
|
23a1f6fa15 | ||
|
|
faa716b8d0 | ||
|
|
9d5c507007 | ||
|
|
dcb6162dcf | ||
|
|
4f07b331e5 | ||
|
|
85ff037647 | ||
|
|
7091a8bed4 | ||
|
|
cf9f4ce4cd | ||
|
|
a7a4e8d9f6 | ||
|
|
28fb8e9038 | ||
|
|
3e114ee729 | ||
|
|
27c464246b | ||
|
|
8c3336600e | ||
|
|
16311546af | ||
|
|
a758672436 | ||
|
|
9bbfb72be6 | ||
|
|
e47432bc9d | ||
|
|
00b6d95752 | ||
|
|
c1a17699db | ||
|
|
3e3b7a9d1a | ||
|
|
28dad93e3e | ||
|
|
f35691f185 | ||
|
|
1ce02ef206 | ||
|
|
135b4fd24e | ||
|
|
8c03663f24 | ||
|
|
3d37adcc9f | ||
|
|
f08a73e191 | ||
|
|
ab29e60ae1 | ||
|
|
7099147871 | ||
|
|
6421671259 | ||
|
|
dec2430b7e | ||
|
|
ff3e56070a | ||
|
|
6eaa4157e8 | ||
|
|
551b99cbcd | ||
|
|
567e4da0c9 | ||
|
|
19d47b45eb | ||
|
|
d4b2df82da | ||
|
|
344b2afe3b | ||
|
|
551edfd8ca | ||
|
|
ade06058f5 | ||
|
|
7703873346 | ||
|
|
eab059583f | ||
|
|
3f3ff8a793 | ||
|
|
87ae4a2063 | ||
|
|
053bcca990 | ||
|
|
8b6ec92f4f | ||
|
|
c8b6d0aa7c | ||
|
|
390b970032 | ||
|
|
24cb632a45 | ||
|
|
7b8424d9bc | ||
|
|
37048a4c9a | ||
|
|
2fea15ef25 | ||
|
|
76e0417ccf | ||
|
|
2ecbb920a6 | ||
|
|
6236547871 | ||
|
|
bb4992f0f9 | ||
|
|
a4d42cf702 | ||
|
|
20f89d14aa | ||
|
|
208a6c23f8 | ||
|
|
709ac97764 | ||
|
|
09b78377e6 | ||
|
|
d5e668a2e6 | ||
|
|
ff0756ff9c | ||
|
|
767f0ca9aa | ||
|
|
57a4c86a79 | ||
|
|
3ed5f51bae | ||
|
|
dc7d015219 | ||
|
|
20757203d9 | ||
|
|
64bb3fb430 | ||
|
|
268392ee9c | ||
|
|
06e6be3c39 | ||
|
|
2a65b41c4e | ||
|
|
e0579857c6 | ||
|
|
c58a010951 | ||
|
|
a4a638208e | ||
|
|
8144f08652 | ||
|
|
cc628d0214 | ||
|
|
ad3b86ba0a | ||
|
|
3777ef95e8 | ||
|
|
a08dd7f58d | ||
|
|
7ac364b407 | ||
|
|
3781e07015 | ||
|
|
07b0c89df6 | ||
|
|
fb15e1568f | ||
|
|
5a856e0b84 | ||
|
|
994dadb84e | ||
|
|
a35ba4886a | ||
|
|
ac859d5607 | ||
|
|
9fee6fa81b | ||
|
|
4c8f7dd73f | ||
|
|
72f2c010ce | ||
|
|
b5132d7f4e | ||
|
|
8cca361441 | ||
|
|
dd09d91c1e | ||
|
|
a890b03163 | ||
|
|
131f344ac4 | ||
|
|
06562e42dd | ||
|
|
e26d8324c3 | ||
|
|
9c639d54b9 | ||
|
|
321cf716f2 | ||
|
|
ab421cf8a7 | ||
|
|
f8c9b78c21 | ||
|
|
8ba4ef8a13 | ||
|
|
df3df3a771 | ||
|
|
118a4179e8 | ||
|
|
251989e318 | ||
|
|
9e03920e61 | ||
|
|
26757cb3b3 | ||
|
|
7fa91675b8 | ||
|
|
bbbbe269a8 | ||
|
|
1466a28a44 | ||
|
|
7f834b2905 | ||
|
|
21d1dd2839 | ||
|
|
7ff6d7eac3 | ||
|
|
50d75f1cf3 | ||
|
|
db27ace069 | ||
|
|
19ff6531d1 | ||
|
|
a31beeac0d | ||
|
|
91d1e38a09 | ||
|
|
924457a48e | ||
|
|
27bb2d798a | ||
|
|
56f835a00f | ||
|
|
ee926fea04 | ||
|
|
824a8b334f | ||
|
|
2675fafb28 |
@@ -0,0 +1,68 @@
|
||||
# Web
|
||||
VITE_API_BASE_URL=http://localhost:3000/api
|
||||
|
||||
# API
|
||||
API_PORT=3000
|
||||
DATABASE_URL=postgresql://cmpp:cmpp_password@localhost:5432/cmpp_platform?schema=public
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
REDIS_URL=redis://127.0.0.1:6379
|
||||
HTTP_API_MASTER_KEY=replace-with-at-least-32-random-characters
|
||||
# Customer-facing HTTP API origin returned by the real backend and shown in copied integration parameters.
|
||||
HTTP_API_PUBLIC_ORIGIN=https://api.example.com
|
||||
# Keep false in production. Only isolated test environments without TLS may opt in to HTTP.
|
||||
HTTP_API_ALLOW_INSECURE_ORIGIN=false
|
||||
API_ENABLE_SEND_WORKER=true
|
||||
API_SEND_WORKER_CONCURRENCY=50
|
||||
API_WORKER_DATABASE_URL=
|
||||
API_WORKER_METRICS_HOST=127.0.0.1
|
||||
API_WORKER_METRICS_PORT=9465
|
||||
CMPP_INBOUND_FAST_PATH_ENABLED=true
|
||||
CMPP_INBOUND_WORKFLOW_WORKER_ENABLED=true
|
||||
API_INBOUND_WORKFLOW_CONCURRENCY=32
|
||||
API_INBOUND_WORKFLOW_BATCH_ENABLED=true
|
||||
API_INBOUND_WORKFLOW_BATCH_SIZE=64
|
||||
API_INBOUND_WORKFLOW_POLL_INTERVAL_MS=100
|
||||
API_INBOUND_WORKFLOW_STALE_SECONDS=300
|
||||
ADMIN_SESSION_IDLE_TIMEOUT_MS=3600000
|
||||
CLIENT_SESSION_IDLE_TIMEOUT_MS=7200000
|
||||
SESSION_LOCK_RECOVERY_MS=14400000
|
||||
SESSION_ABSOLUTE_TIMEOUT_MS=43200000
|
||||
SESSION_RECENT_AUTH_MS=1800000
|
||||
# First-rollout safety only: both values must be set and the deadline must be within two hours.
|
||||
# Omit them during normal operation so all password writes use scrypt.
|
||||
PASSWORD_HASH_LEGACY_TRANSITION=false
|
||||
PASSWORD_HASH_LEGACY_WRITE_UNTIL=
|
||||
OPERATION_LOG_ARCHIVE_ENABLED=true
|
||||
OPERATION_LOG_RETENTION_DAYS=180
|
||||
OPERATION_LOG_ARCHIVE_BATCH_SIZE=1000
|
||||
OPERATION_LOG_ARCHIVE_MAX_BATCHES=20
|
||||
OPERATION_LOG_ARCHIVE_INTERVAL_MS=86400000
|
||||
SMS_RECEIPT_TIMEOUT_SCAN_ENABLED=true
|
||||
SMS_RECEIPT_TIMEOUT_HOURS=72
|
||||
SMS_RECEIPT_TIMEOUT_SCAN_INTERVAL_MS=300000
|
||||
# System monitoring reads only fixed queries from a loopback Prometheus instance.
|
||||
PROMETHEUS_URL=http://127.0.0.1:9090
|
||||
PROMETHEUS_QUERY_TIMEOUT_MS=5000
|
||||
# Local HTTP development only. Production must use HTTPS and true.
|
||||
SESSION_COOKIE_SECURE=false
|
||||
MINIO_ENDPOINT=localhost:9000
|
||||
MINIO_ACCESS_KEY=cmpp_minio
|
||||
MINIO_SECRET_KEY=cmpp_minio_password
|
||||
MINIO_BUCKET=cmpp-platform
|
||||
|
||||
# Gateway
|
||||
GATEWAY_HEALTH_ADDR=:8090
|
||||
GATEWAY_REDIS_ADDR=127.0.0.1:6379
|
||||
GATEWAY_CMPP_VERSION=3.0
|
||||
GATEWAY_CMPP_ADDR=127.0.0.1:7890
|
||||
GATEWAY_CMPP_USER=900001
|
||||
GATEWAY_CMPP_PASSWORD=888888
|
||||
GATEWAY_SUBMIT_WORKER_CONCURRENCY=64
|
||||
GATEWAY_SUBMIT_RESULT_STREAM=gateway.submit.results
|
||||
GATEWAY_SUBMIT_RESULT_GROUP=cmpp-api-callback
|
||||
GATEWAY_SUBMIT_RESULT_CONSUMER=gateway-1
|
||||
GATEWAY_SUBMIT_RESULT_WORKER_CONCURRENCY=8
|
||||
GATEWAY_CALLBACK_BATCH_ENABLED=false
|
||||
GATEWAY_CMPP_INBOUND_MAX_CONCURRENCY=64
|
||||
CMPP_DOWNSTREAM_ACK_TIMEOUT_SECONDS=30
|
||||
@@ -0,0 +1 @@
|
||||
*.sh text eol=lf
|
||||
@@ -0,0 +1,36 @@
|
||||
name: CSS quality
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
css-quality:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Resolve the complete change range
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
PR_BASE: ${{ github.event.pull_request.base.sha }}
|
||||
PUSH_BEFORE: ${{ github.event.before }}
|
||||
run: |
|
||||
if [ "$EVENT_NAME" = pull_request ]; then
|
||||
base=$(git merge-base "$PR_BASE" HEAD)
|
||||
elif [ "$PUSH_BEFORE" = 0000000000000000000000000000000000000000 ]; then
|
||||
base=$(git hash-object -t tree /dev/null)
|
||||
else
|
||||
git cat-file -e "$PUSH_BEFORE^{commit}"
|
||||
base=$PUSH_BEFORE
|
||||
fi
|
||||
echo "QUALITY_BASE_REF=$base" >> "$GITHUB_ENV"
|
||||
- name: Verify changed formatting and CSS ownership
|
||||
run: npm run format:check && npm run style:check && npm run css:verify
|
||||
+13
@@ -2,12 +2,25 @@ node_modules/
|
||||
dist/
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.local
|
||||
.local-tools/
|
||||
.local-data/
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
logs/
|
||||
coverage/
|
||||
gateway/gateway.exe
|
||||
dump.rdb
|
||||
*.tsbuildinfo
|
||||
outputs/
|
||||
tmp_*.py
|
||||
tmp_*.ps1
|
||||
tmp_*.sh
|
||||
|
||||
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
dist
|
||||
coverage
|
||||
api/dist
|
||||
api/vendor
|
||||
package-lock.json
|
||||
docs
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"extends": ["stylelint-config-standard"],
|
||||
"ignoreFiles": ["dist/**/*.css"],
|
||||
"rules": {
|
||||
"alpha-value-notation": null,
|
||||
"color-function-notation": null,
|
||||
"declaration-block-single-line-max-declarations": null,
|
||||
"media-feature-range-notation": null,
|
||||
"no-descending-specificity": null,
|
||||
"selector-class-pattern": null
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"src/apps/admin/channels/AdminChannelsPage.css",
|
||||
"src/apps/admin/enterprise-applications/AdminEnterpriseApplicationsPage.css",
|
||||
"src/apps/admin/security-detection/AdminSecurityDetectionPage.css",
|
||||
"src/apps/admin/sms-records/AdminSmsRecordsPage.css",
|
||||
"src/apps/admin/sms-task-progress/AdminSmsTaskProgressPage.css",
|
||||
"src/apps/admin/system-monitoring/AdminSystemMonitoringPage.css",
|
||||
"src/apps/client/ClientUsersPage.css",
|
||||
"src/styles/admin.css",
|
||||
"src/styles/client.css",
|
||||
"src/styles/components.css",
|
||||
"src/styles/domains/01-operations-dashboard.css",
|
||||
"src/styles/domains/02-client-sending.css",
|
||||
"src/styles/domains/03-client-records.css",
|
||||
"src/styles/domains/04-signatures.css",
|
||||
"src/styles/domains/05-templates.css",
|
||||
"src/styles/domains/06-auth-enterprise.css",
|
||||
"src/styles/domains/07-admin-operations.css",
|
||||
"src/styles/domains/08-reporting.css",
|
||||
"src/styles/domains/09-channels.css",
|
||||
"src/styles/domains/10-signature-quality.css",
|
||||
"src/styles/domains/11-deliveries-reporting.css",
|
||||
"src/styles/domains/12-admin-configuration.css",
|
||||
"src/styles/domains/13-client-signatures.css",
|
||||
"src/styles/domains/14-responsive-requeue.css",
|
||||
"src/styles/domains/index.css",
|
||||
"src/styles/reset.css",
|
||||
"src/styles/shell.css",
|
||||
"src/styles/tokens.css"
|
||||
],
|
||||
"rules": {
|
||||
"at-rule-empty-line-before": null,
|
||||
"block-no-empty": null,
|
||||
"color-function-alias-notation": null,
|
||||
"color-hex-length": null,
|
||||
"comment-empty-line-before": null,
|
||||
"custom-property-pattern": null,
|
||||
"declaration-block-no-redundant-longhand-properties": null,
|
||||
"declaration-empty-line-before": null,
|
||||
"declaration-property-value-keyword-no-deprecated": null,
|
||||
"font-family-name-quotes": null,
|
||||
"function-url-quotes": null,
|
||||
"import-notation": null,
|
||||
"keyframes-name-pattern": null,
|
||||
"length-zero-no-unit": null,
|
||||
"no-duplicate-selectors": null,
|
||||
"number-max-precision": null,
|
||||
"property-no-vendor-prefix": null,
|
||||
"rule-empty-line-before": null,
|
||||
"selector-attribute-quotes": null,
|
||||
"selector-id-pattern": null,
|
||||
"selector-not-notation": null,
|
||||
"selector-pseudo-class-no-unknown": null,
|
||||
"selector-type-no-unknown": null,
|
||||
"shorthand-property-no-redundant-values": null,
|
||||
"value-keyword-case": null,
|
||||
"value-no-vendor-prefix": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# CMPP 平台仓库开发约束
|
||||
|
||||
本文件适用于整个仓库。进入子目录工作时,如果存在更具体的 `AGENTS.md`,还应同时遵守子目录规范。
|
||||
|
||||
## 开始工作前
|
||||
|
||||
- 先检查当前分支、最近提交、远端差异以及 staged、unstaged、untracked 文件。
|
||||
- 保留其他会话和用户已有修改;禁止未经授权执行 reset、清理、覆盖、切分支、推送或部署。
|
||||
- 功能事实必须以当前代码、真实 API、PostgreSQL、Redis、MinIO、Gateway 和目标环境为准,交接记录只作为线索。
|
||||
- 不得发送、补发、重投或重新入队短信;不得擅自修改余额、通道或客户配置。
|
||||
|
||||
## CSS 任务强制阅读
|
||||
|
||||
凡新增、修改、迁移或审查 CSS,必须先完整阅读:
|
||||
|
||||
1. `docs/css-development-guidelines.md`;
|
||||
2. 涉及存量拆分时,再阅读 `docs/global-css-modularization-plan-20260904.md`。
|
||||
|
||||
## CSS 所有权
|
||||
|
||||
- 设计变量归 `src/styles/tokens.css`。
|
||||
- 浏览器重置和标签基础规则归 `src/styles/reset.css`。
|
||||
- AppShell、侧栏、顶栏、导航和页面骨架归 `src/styles/shell.css`。
|
||||
- 跨业务复用且语义一致的公共组件样式归 `src/styles/components/`;迁移完成前的既有公共规则可继续位于明确登记的共享样式文件。
|
||||
- 同一业务域多个页面共享的样式归业务域目录,并由业务域根类名限定。
|
||||
- 只服务一个页面、弹窗或局部组件的样式必须放在其 TSX 同目录或对应业务目录,由所有者直接 import。
|
||||
- `src/styles/global.css` 是存量兼容文件:只允许迁出、删除和保持视觉等价所必需的修正,不得新增页面或业务选择器。例外必须在变更说明中写明原因、影响范围和清理条件。
|
||||
|
||||
## CSS 禁止事项
|
||||
|
||||
- 禁止把新页面、新弹窗或新业务组件的选择器写入 `global.css`。
|
||||
- 禁止使用无业务根节点限定的标签选择器或短通用类名影响其他页面。
|
||||
- 禁止通过新增 `!important`、提高 specificity 或依赖偶然加载顺序掩盖归属和级联问题;第三方不可控样式等例外必须记录原因、影响范围和移除条件。
|
||||
- 禁止把公共组件样式建立在业务页面样式之上。
|
||||
- 禁止对 `global.css` 执行整文件格式化,或在迁移提交中夹带视觉改版和无关重排。
|
||||
- 禁止仅凭类名前缀批量移动选择器;必须同时核对 TSX 引用、其他 CSS 定义、媒体查询和真实 DOM。
|
||||
|
||||
## CSS 变更验收
|
||||
|
||||
- 提交前检查 staged diff,确保只包含本轮目标文件或精确 hunk。
|
||||
- 运行与范围匹配的定向测试、前端全量测试、TypeScript、生产构建、仓库当前提供的格式和样式门禁以及 `git diff --check`;自动门禁尚未覆盖的项目按日常规范人工检查并记录。
|
||||
- 页面级变更至少检查 1600×1000、1366×768 和 390×844;覆盖首次进入、刷新、跨路由切换及相关交互状态。
|
||||
- 使用真实 API 和测试环境完成最终功能验收;构建成功、组件测试或隔离截图不能代替真实页面验收。
|
||||
- 纯 CSS 拆分不得改变计算样式。发现差异时先恢复原级联关系,不得顺手调整设计。
|
||||
@@ -0,0 +1,21 @@
|
||||
# CMPP Platform API Spike
|
||||
|
||||
阶段 0 的 `api/` 目录用于 NestJS + BullMQ 通信 Spike。当前只定义边界,不展开业务模块。
|
||||
|
||||
## 阶段 0 职责
|
||||
|
||||
- 生产 `cmpp.submit.commands` 队列消息。
|
||||
- 消费 `cmpp.submit.results`、`cmpp.receipt.events`、`cmpp.uplink.events`。
|
||||
- 使用 `traceId`、`messageId`、`channelId` 记录最小链路状态。
|
||||
- 为 500 条/秒 Spike 提供批量入队脚本或测试入口。
|
||||
|
||||
## 不在阶段 0 实现
|
||||
|
||||
- 认证、权限、租户完整模型。
|
||||
- 企业、应用、签名、模板 CRUD。
|
||||
- 风控、审核、报备、计费真实业务规则。
|
||||
- PostgreSQL/Prisma 正式 schema。
|
||||
|
||||
## 队列契约
|
||||
|
||||
以 `docs/contracts/gateway-queue-messages.schema.json` 和 `docs/contracts/examples/` 为准。
|
||||
@@ -0,0 +1,20 @@
|
||||
/** @type {import('jest').Config} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'^.+\\.ts$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
||||
},
|
||||
roots: ['<rootDir>/src'],
|
||||
testMatch: ['**/*.spec.ts'],
|
||||
moduleFileExtensions: ['ts', 'js', 'json'],
|
||||
clearMocks: true,
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
statements: 59,
|
||||
branches: 50,
|
||||
functions: 60,
|
||||
lines: 62,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
const base = require('./jest.config.cjs');
|
||||
|
||||
module.exports = {
|
||||
...base,
|
||||
collectCoverageFrom: [
|
||||
'src/auth/auth.service.ts',
|
||||
'src/auth/session.service.ts',
|
||||
'src/common/bounded-json-object.validator.ts',
|
||||
'src/files/client-files.dto.ts',
|
||||
'src/open-api/client-open-api.dto.ts',
|
||||
'src/operations/client-operations.dto.ts',
|
||||
'src/users/client-user.dto.ts',
|
||||
],
|
||||
coverageThreshold: {
|
||||
global: { statements: 80, branches: 70, functions: 80, lines: 80 },
|
||||
},
|
||||
};
|
||||
Generated
+9496
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "cmpp-platform-api",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.build.json",
|
||||
"test": "jest --runInBand",
|
||||
"test:coverage": "jest --runInBand --coverage",
|
||||
"test:incremental-coverage": "jest --runInBand --coverage --config jest.incremental.config.cjs",
|
||||
"test:watch": "jest --watch",
|
||||
"start": "node dist/main.js",
|
||||
"start:dev": "ts-node src/main.ts",
|
||||
"start:report-material-worker": "node dist/report-material-analysis-worker.js",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate:dev": "prisma migrate dev",
|
||||
"prisma:migrate:deploy": "prisma migrate deploy"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^11.1.28",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "^11.1.28",
|
||||
"@nestjs/platform-express": "^11.1.28",
|
||||
"@nestjs/swagger": "11.4.7",
|
||||
"@prisma/adapter-pg": "^7.9.0",
|
||||
"@prisma/client": "^7.9.0",
|
||||
"bullmq": "^5.79.2",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.3",
|
||||
"brace-expansion": "file:vendor/brace-expansion-compat",
|
||||
"exceljs": "^4.4.0",
|
||||
"ioredis": "^5.11.1",
|
||||
"jszip": "^3.10.1",
|
||||
"minio": "^8.0.7",
|
||||
"pg": "^8.22.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^25.9.3",
|
||||
"jest": "^30.4.2",
|
||||
"prisma": "^7.9.0",
|
||||
"ts-jest": "^29.4.11",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^6.0.3"
|
||||
},
|
||||
"overrides": {
|
||||
"brace-expansion": "$brace-expansion",
|
||||
"exceljs": {
|
||||
"uuid": "11.1.1"
|
||||
},
|
||||
"find-my-way": "9.7.0",
|
||||
"fast-uri": "3.1.5",
|
||||
"js-yaml": "4.3.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from 'prisma/config';
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL?.trim();
|
||||
const allowDevelopmentDefault = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test';
|
||||
if (!databaseUrl && !allowDevelopmentDefault) {
|
||||
throw new Error('DATABASE_URL is required outside development and test environments');
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
schema: 'prisma/schema.prisma',
|
||||
migrations: {
|
||||
path: 'prisma/migrations',
|
||||
},
|
||||
datasource: {
|
||||
url: databaseUrl ?? 'postgresql://cmpp:cmpp_password@localhost:5432/cmpp_platform?schema=public',
|
||||
},
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "SmsBatchTask" ADD COLUMN "scheduledAt" TIMESTAMP(3);
|
||||
ALTER TABLE "SmsBatchTask" ADD COLUMN "canceledAt" TIMESTAMP(3);
|
||||
CREATE INDEX "SmsBatchTask_status_scheduledAt_idx" ON "SmsBatchTask"("status", "scheduledAt");
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
ALTER TABLE "Tenant" ADD COLUMN "certificationStatus" TEXT NOT NULL DEFAULT 'approved';
|
||||
|
||||
CREATE TABLE "EnterpriseCertification" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"companyName" TEXT NOT NULL,
|
||||
"licenseNo" TEXT,
|
||||
"contactName" TEXT,
|
||||
"contactPhone" TEXT,
|
||||
"materials" JSONB,
|
||||
"status" TEXT NOT NULL DEFAULT 'pending',
|
||||
"rejectReason" TEXT,
|
||||
"reviewerId" TEXT,
|
||||
"submittedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"reviewedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "EnterpriseCertification_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "CmppConnectionState" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT,
|
||||
"channelId" TEXT NOT NULL,
|
||||
"connectionId" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'disconnected',
|
||||
"desiredConnections" INTEGER NOT NULL DEFAULT 1,
|
||||
"currentConnections" INTEGER NOT NULL DEFAULT 0,
|
||||
"lastConnectedAt" TIMESTAMP(3),
|
||||
"lastDisconnectedAt" TIMESTAMP(3),
|
||||
"lastHeartbeatAt" TIMESTAMP(3),
|
||||
"reconnectCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"lastError" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "CmppConnectionState_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "EnterpriseCertification_tenantId_status_createdAt_idx" ON "EnterpriseCertification"("tenantId", "status", "createdAt");
|
||||
CREATE UNIQUE INDEX "CmppConnectionState_channelId_connectionId_key" ON "CmppConnectionState"("channelId", "connectionId");
|
||||
CREATE INDEX "CmppConnectionState_tenantId_status_idx" ON "CmppConnectionState"("tenantId", "status");
|
||||
CREATE INDEX "CmppConnectionState_channelId_status_idx" ON "CmppConnectionState"("channelId", "status");
|
||||
|
||||
ALTER TABLE "EnterpriseCertification" ADD CONSTRAINT "EnterpriseCertification_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "CmppConnectionState" ADD CONSTRAINT "CmppConnectionState_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "CmppConnectionState" ADD CONSTRAINT "CmppConnectionState_channelId_fkey" FOREIGN KEY ("channelId") REFERENCES "SmsChannel"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,10 @@
|
||||
ALTER TABLE "User"
|
||||
ADD COLUMN "email" TEXT,
|
||||
ADD COLUMN "phone" TEXT,
|
||||
ADD COLUMN "failedLoginCount" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "lockedUntil" TIMESTAMP(3),
|
||||
ADD COLUMN "lastLoginAt" TIMESTAMP(3),
|
||||
ADD COLUMN "deletedAt" TIMESTAMP(3);
|
||||
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
CREATE UNIQUE INDEX "User_phone_key" ON "User"("phone");
|
||||
@@ -0,0 +1,20 @@
|
||||
-- Channel-group routing rules for first-version send-chain failover.
|
||||
ALTER TABLE "SmsChannel" ADD COLUMN "sendRegion" TEXT NOT NULL DEFAULT '全国';
|
||||
|
||||
ALTER TABLE "SmsChannelGroup" ADD COLUMN "retryEnabled" BOOLEAN NOT NULL DEFAULT true;
|
||||
ALTER TABLE "SmsChannelGroup" ADD COLUMN "retryTimeLimitHours" INTEGER NOT NULL DEFAULT 72;
|
||||
|
||||
CREATE TABLE "PhoneCarrierRule" (
|
||||
"id" TEXT NOT NULL,
|
||||
"carrier" TEXT NOT NULL,
|
||||
"pattern" TEXT NOT NULL,
|
||||
"priority" INTEGER NOT NULL DEFAULT 100,
|
||||
"status" TEXT NOT NULL DEFAULT 'active',
|
||||
"remark" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "PhoneCarrierRule_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "PhoneCarrierRule_status_priority_idx" ON "PhoneCarrierRule"("status", "priority");
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "SmsChannelGroup" ADD COLUMN "carrier" TEXT NOT NULL DEFAULT 'mobile';
|
||||
|
||||
CREATE UNIQUE INDEX "SmsChannelGroupItem_groupId_province_key" ON "SmsChannelGroupItem"("groupId", "province");
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "SmsApplication" ADD COLUMN "customerUnitPrice" INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Make CMPP connection states application-scoped for enterprise application status.
|
||||
ALTER TABLE "CmppConnectionState" ADD COLUMN "applicationId" TEXT;
|
||||
|
||||
ALTER TABLE "CmppConnectionState" ADD CONSTRAINT "CmppConnectionState_applicationId_fkey"
|
||||
FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
DROP INDEX IF EXISTS "CmppConnectionState_channelId_connectionId_key";
|
||||
CREATE UNIQUE INDEX "CmppConnectionState_applicationId_channelId_connectionId_key"
|
||||
ON "CmppConnectionState"("applicationId", "channelId", "connectionId");
|
||||
CREATE INDEX "CmppConnectionState_applicationId_status_idx"
|
||||
ON "CmppConnectionState"("applicationId", "status");
|
||||
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE "SmsChannelGroup" ADD COLUMN "retryTimeLimitMinutes" INTEGER NOT NULL DEFAULT 720;
|
||||
|
||||
UPDATE "SmsChannelGroup"
|
||||
SET "retryTimeLimitMinutes" = LEAST(GREATEST("retryTimeLimitHours", 1), 72) * 60;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "SmsApplication" ADD COLUMN "queuePriority" TEXT NOT NULL DEFAULT 'normal';
|
||||
ALTER TABLE "SmsMessageRecord" ADD COLUMN "queuePriority" TEXT NOT NULL DEFAULT 'normal';
|
||||
@@ -0,0 +1,13 @@
|
||||
ALTER TABLE "SmsApplication" ADD COLUMN "cmppAccount" TEXT;
|
||||
|
||||
WITH numbered AS (
|
||||
SELECT id, row_number() OVER (ORDER BY "createdAt", id) AS rn
|
||||
FROM "SmsApplication"
|
||||
)
|
||||
UPDATE "SmsApplication" app
|
||||
SET "cmppAccount" = lpad(((100000 + numbered.rn) % 1000000)::text, 6, '0')
|
||||
FROM numbered
|
||||
WHERE app.id = numbered.id;
|
||||
|
||||
ALTER TABLE "SmsApplication" ALTER COLUMN "cmppAccount" SET NOT NULL;
|
||||
CREATE UNIQUE INDEX "SmsApplication_cmppAccount_key" ON "SmsApplication"("cmppAccount");
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
-- Add customer-side downstream delivery persistence and uplink matching fields.
|
||||
|
||||
ALTER TABLE "SmsUplinkMessage"
|
||||
ADD COLUMN "applicationId" TEXT,
|
||||
ADD COLUMN "messageRecordId" TEXT,
|
||||
ADD COLUMN "matchStatus" TEXT NOT NULL DEFAULT 'unmatched',
|
||||
ADD COLUMN "matchReason" TEXT;
|
||||
|
||||
ALTER TABLE "SmsUplinkMessage"
|
||||
ADD CONSTRAINT "SmsUplinkMessage_applicationId_fkey"
|
||||
FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "SmsUplinkMessage"
|
||||
ADD CONSTRAINT "SmsUplinkMessage_messageRecordId_fkey"
|
||||
FOREIGN KEY ("messageRecordId") REFERENCES "SmsMessageRecord"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
CREATE INDEX "SmsUplinkMessage_applicationId_receivedAt_idx" ON "SmsUplinkMessage"("applicationId", "receivedAt");
|
||||
CREATE INDEX "SmsUplinkMessage_messageRecordId_idx" ON "SmsUplinkMessage"("messageRecordId");
|
||||
CREATE INDEX "SmsUplinkMessage_matchStatus_idx" ON "SmsUplinkMessage"("matchStatus");
|
||||
|
||||
CREATE TABLE "CmppDownstreamDelivery" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"messageRecordId" TEXT,
|
||||
"messageId" TEXT,
|
||||
"deliveryType" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'pending',
|
||||
"payload" JSONB NOT NULL,
|
||||
"retryCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"nextRetryAt" TIMESTAMP(3),
|
||||
"deliveredAt" TIMESTAMP(3),
|
||||
"lastError" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "CmppDownstreamDelivery_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
ALTER TABLE "CmppDownstreamDelivery"
|
||||
ADD CONSTRAINT "CmppDownstreamDelivery_tenantId_fkey"
|
||||
FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "CmppDownstreamDelivery"
|
||||
ADD CONSTRAINT "CmppDownstreamDelivery_applicationId_fkey"
|
||||
FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "CmppDownstreamDelivery"
|
||||
ADD CONSTRAINT "CmppDownstreamDelivery_messageRecordId_fkey"
|
||||
FOREIGN KEY ("messageRecordId") REFERENCES "SmsMessageRecord"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
CREATE INDEX "CmppDownstreamDelivery_tenantId_status_createdAt_idx" ON "CmppDownstreamDelivery"("tenantId", "status", "createdAt");
|
||||
CREATE INDEX "CmppDownstreamDelivery_applicationId_status_createdAt_idx" ON "CmppDownstreamDelivery"("applicationId", "status", "createdAt");
|
||||
CREATE INDEX "CmppDownstreamDelivery_messageId_idx" ON "CmppDownstreamDelivery"("messageId");
|
||||
CREATE INDEX "CmppDownstreamDelivery_messageRecordId_idx" ON "CmppDownstreamDelivery"("messageRecordId");
|
||||
CREATE INDEX "CmppDownstreamDelivery_deliveryType_status_idx" ON "CmppDownstreamDelivery"("deliveryType", "status");
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "SmsApplication"
|
||||
ADD COLUMN "cmppMaxConnections" INTEGER NOT NULL DEFAULT 1,
|
||||
ADD COLUMN "cmppWindowSize" INTEGER NOT NULL DEFAULT 16;
|
||||
@@ -0,0 +1,38 @@
|
||||
CREATE TABLE "GatewaySubmitDeadLetter" (
|
||||
"id" TEXT NOT NULL,
|
||||
"streamMessageId" TEXT NOT NULL,
|
||||
"tenantId" TEXT,
|
||||
"applicationId" TEXT,
|
||||
"channelId" TEXT,
|
||||
"traceId" TEXT,
|
||||
"messageId" TEXT,
|
||||
"submitId" TEXT,
|
||||
"status" TEXT NOT NULL DEFAULT 'pending',
|
||||
"failureCode" TEXT NOT NULL,
|
||||
"failureMessage" TEXT NOT NULL,
|
||||
"attempts" INTEGER NOT NULL DEFAULT 1,
|
||||
"maxAttempts" INTEGER NOT NULL DEFAULT 3,
|
||||
"commandPayload" JSONB,
|
||||
"rawPayload" TEXT,
|
||||
"manualRetryCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"lastRetryStreamId" TEXT,
|
||||
"lastRetriedAt" TIMESTAMP(3),
|
||||
"resolvedAt" TIMESTAMP(3),
|
||||
"resolvedStatus" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "GatewaySubmitDeadLetter_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "GatewaySubmitDeadLetter_streamMessageId_key" ON "GatewaySubmitDeadLetter"("streamMessageId");
|
||||
CREATE INDEX "GatewaySubmitDeadLetter_status_createdAt_idx" ON "GatewaySubmitDeadLetter"("status", "createdAt");
|
||||
CREATE INDEX "GatewaySubmitDeadLetter_tenantId_status_createdAt_idx" ON "GatewaySubmitDeadLetter"("tenantId", "status", "createdAt");
|
||||
CREATE INDEX "GatewaySubmitDeadLetter_applicationId_status_createdAt_idx" ON "GatewaySubmitDeadLetter"("applicationId", "status", "createdAt");
|
||||
CREATE INDEX "GatewaySubmitDeadLetter_channelId_status_createdAt_idx" ON "GatewaySubmitDeadLetter"("channelId", "status", "createdAt");
|
||||
CREATE INDEX "GatewaySubmitDeadLetter_messageId_idx" ON "GatewaySubmitDeadLetter"("messageId");
|
||||
CREATE INDEX "GatewaySubmitDeadLetter_submitId_idx" ON "GatewaySubmitDeadLetter"("submitId");
|
||||
|
||||
ALTER TABLE "GatewaySubmitDeadLetter" ADD CONSTRAINT "GatewaySubmitDeadLetter_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "GatewaySubmitDeadLetter" ADD CONSTRAINT "GatewaySubmitDeadLetter_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "GatewaySubmitDeadLetter" ADD CONSTRAINT "GatewaySubmitDeadLetter_channelId_fkey" FOREIGN KEY ("channelId") REFERENCES "SmsChannel"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
CREATE TABLE "GatewayDownstreamRecoveryStatus" (
|
||||
"id" TEXT NOT NULL,
|
||||
"account" TEXT NOT NULL,
|
||||
"tenantId" TEXT,
|
||||
"applicationId" TEXT,
|
||||
"gatewayInstanceId" TEXT,
|
||||
"state" TEXT NOT NULL,
|
||||
"lastAttemptAt" TIMESTAMP(3),
|
||||
"lastSuccessAt" TIMESTAMP(3),
|
||||
"lastFailureAt" TIMESTAMP(3),
|
||||
"nextRetryAt" TIMESTAMP(3),
|
||||
"attemptCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"lastError" TEXT,
|
||||
"lastSkipReason" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "GatewayDownstreamRecoveryStatus_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "GatewayDownstreamRecoveryStatus_account_key" ON "GatewayDownstreamRecoveryStatus"("account");
|
||||
CREATE INDEX "GatewayDownstreamRecoveryStatus_tenantId_state_updatedAt_idx" ON "GatewayDownstreamRecoveryStatus"("tenantId", "state", "updatedAt");
|
||||
CREATE INDEX "GatewayDownstreamRecoveryStatus_applicationId_state_updatedAt_idx" ON "GatewayDownstreamRecoveryStatus"("applicationId", "state", "updatedAt");
|
||||
CREATE INDEX "GatewayDownstreamRecoveryStatus_state_updatedAt_idx" ON "GatewayDownstreamRecoveryStatus"("state", "updatedAt");
|
||||
CREATE INDEX "GatewayDownstreamRecoveryStatus_nextRetryAt_idx" ON "GatewayDownstreamRecoveryStatus"("nextRetryAt");
|
||||
|
||||
ALTER TABLE "GatewayDownstreamRecoveryStatus"
|
||||
ADD CONSTRAINT "GatewayDownstreamRecoveryStatus_tenantId_fkey"
|
||||
FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "GatewayDownstreamRecoveryStatus"
|
||||
ADD CONSTRAINT "GatewayDownstreamRecoveryStatus_applicationId_fkey"
|
||||
FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE "GatewayDownstreamRecoveryStatus"
|
||||
ADD COLUMN "failureCategory" TEXT;
|
||||
|
||||
CREATE INDEX "GatewayDownstreamRecoveryStatus_failureCategory_updatedAt_idx"
|
||||
ON "GatewayDownstreamRecoveryStatus"("failureCategory", "updatedAt");
|
||||
@@ -0,0 +1,6 @@
|
||||
ALTER TABLE "GatewayDownstreamRecoveryStatus"
|
||||
ADD COLUMN "lockOwner" TEXT,
|
||||
ADD COLUMN "lockExpiresAt" TIMESTAMP(3);
|
||||
|
||||
CREATE INDEX "GatewayDownstreamRecoveryStatus_lockOwner_lockExpiresAt_idx"
|
||||
ON "GatewayDownstreamRecoveryStatus"("lockOwner", "lockExpiresAt");
|
||||
@@ -0,0 +1,64 @@
|
||||
CREATE TABLE "SmsMessageSegmentAudit" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"batchTaskId" TEXT,
|
||||
"messageRecordId" TEXT NOT NULL,
|
||||
"submitRecordId" TEXT,
|
||||
"channelId" TEXT,
|
||||
"submitId" TEXT NOT NULL,
|
||||
"attempt" INTEGER NOT NULL DEFAULT 0,
|
||||
"segmentTotal" INTEGER NOT NULL DEFAULT 1,
|
||||
"segmentIndex" INTEGER NOT NULL DEFAULT 1,
|
||||
"sequenceId" INTEGER,
|
||||
"gatewayMessageId" TEXT,
|
||||
"submitStatus" TEXT NOT NULL DEFAULT 'queued',
|
||||
"receiptStatus" TEXT,
|
||||
"rawStatus" TEXT,
|
||||
"compensationType" TEXT,
|
||||
"errorCode" TEXT,
|
||||
"errorMessage" TEXT,
|
||||
"submittedAt" TIMESTAMP(3),
|
||||
"deliveredAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SmsMessageSegmentAudit_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "SmsMessageSegmentAudit_messageRecordId_submitId_segmentIndex_key"
|
||||
ON "SmsMessageSegmentAudit"("messageRecordId", "submitId", "segmentIndex");
|
||||
|
||||
CREATE INDEX "SmsMessageSegmentAudit_tenantId_createdAt_idx"
|
||||
ON "SmsMessageSegmentAudit"("tenantId", "createdAt");
|
||||
|
||||
CREATE INDEX "SmsMessageSegmentAudit_messageRecordId_segmentIndex_idx"
|
||||
ON "SmsMessageSegmentAudit"("messageRecordId", "segmentIndex");
|
||||
|
||||
CREATE INDEX "SmsMessageSegmentAudit_submitRecordId_idx"
|
||||
ON "SmsMessageSegmentAudit"("submitRecordId");
|
||||
|
||||
CREATE INDEX "SmsMessageSegmentAudit_gatewayMessageId_idx"
|
||||
ON "SmsMessageSegmentAudit"("gatewayMessageId");
|
||||
|
||||
CREATE INDEX "SmsMessageSegmentAudit_submitStatus_receiptStatus_idx"
|
||||
ON "SmsMessageSegmentAudit"("submitStatus", "receiptStatus");
|
||||
|
||||
ALTER TABLE "SmsMessageSegmentAudit"
|
||||
ADD CONSTRAINT "SmsMessageSegmentAudit_tenantId_fkey"
|
||||
FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "SmsMessageSegmentAudit"
|
||||
ADD CONSTRAINT "SmsMessageSegmentAudit_batchTaskId_fkey"
|
||||
FOREIGN KEY ("batchTaskId") REFERENCES "SmsBatchTask"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "SmsMessageSegmentAudit"
|
||||
ADD CONSTRAINT "SmsMessageSegmentAudit_messageRecordId_fkey"
|
||||
FOREIGN KEY ("messageRecordId") REFERENCES "SmsMessageRecord"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "SmsMessageSegmentAudit"
|
||||
ADD CONSTRAINT "SmsMessageSegmentAudit_submitRecordId_fkey"
|
||||
FOREIGN KEY ("submitRecordId") REFERENCES "SmsSubmitRecord"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "SmsMessageSegmentAudit"
|
||||
ADD CONSTRAINT "SmsMessageSegmentAudit_channelId_fkey"
|
||||
FOREIGN KEY ("channelId") REFERENCES "SmsChannel"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,45 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "SmsUplinkMatchCandidate" (
|
||||
"id" TEXT NOT NULL,
|
||||
"uplinkMessageId" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"messageRecordId" TEXT,
|
||||
"matchSource" TEXT NOT NULL,
|
||||
"confidence" INTEGER NOT NULL DEFAULT 50,
|
||||
"reason" TEXT,
|
||||
"status" TEXT NOT NULL DEFAULT 'pending',
|
||||
"claimedAt" TIMESTAMP(3),
|
||||
"claimedById" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SmsUplinkMatchCandidate_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "SmsUplinkMatchCandidate_uplinkMessageId_applicationId_messageRecordId_key" ON "SmsUplinkMatchCandidate"("uplinkMessageId", "applicationId", "messageRecordId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SmsUplinkMatchCandidate_uplinkMessageId_status_idx" ON "SmsUplinkMatchCandidate"("uplinkMessageId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SmsUplinkMatchCandidate_tenantId_status_createdAt_idx" ON "SmsUplinkMatchCandidate"("tenantId", "status", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SmsUplinkMatchCandidate_applicationId_status_createdAt_idx" ON "SmsUplinkMatchCandidate"("applicationId", "status", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SmsUplinkMatchCandidate_messageRecordId_idx" ON "SmsUplinkMatchCandidate"("messageRecordId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SmsUplinkMatchCandidate" ADD CONSTRAINT "SmsUplinkMatchCandidate_uplinkMessageId_fkey" FOREIGN KEY ("uplinkMessageId") REFERENCES "SmsUplinkMessage"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SmsUplinkMatchCandidate" ADD CONSTRAINT "SmsUplinkMatchCandidate_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SmsUplinkMatchCandidate" ADD CONSTRAINT "SmsUplinkMatchCandidate_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SmsUplinkMatchCandidate" ADD CONSTRAINT "SmsUplinkMatchCandidate_messageRecordId_fkey" FOREIGN KEY ("messageRecordId") REFERENCES "SmsMessageRecord"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "SmsChannel" ALTER COLUMN "cmppVersion" SET DEFAULT '2.0';
|
||||
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE "SmsMessageRecord" ALTER COLUMN "tenantId" DROP NOT NULL;
|
||||
ALTER TABLE "SmsMessageRecord" ALTER COLUMN "batchTaskId" DROP NOT NULL;
|
||||
|
||||
ALTER TABLE "SmsSubmitRecord" ALTER COLUMN "tenantId" DROP NOT NULL;
|
||||
ALTER TABLE "SmsSubmitRecord" ALTER COLUMN "batchTaskId" DROP NOT NULL;
|
||||
|
||||
ALTER TABLE "SmsMessageSegmentAudit" ALTER COLUMN "tenantId" DROP NOT NULL;
|
||||
|
||||
ALTER TABLE "SmsReceiptRecord" ALTER COLUMN "tenantId" DROP NOT NULL;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "SmsApplication"
|
||||
ADD COLUMN "interfaceEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN "interfaceType" TEXT NOT NULL DEFAULT 'cmpp20';
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE "SmsApplication" ADD COLUMN "cmppEnterpriseCode" TEXT;
|
||||
|
||||
UPDATE "SmsApplication" AS app
|
||||
SET "cmppEnterpriseCode" = tenant."code",
|
||||
"secretHash" = substring(md5(random()::text || clock_timestamp()::text || app."id") from 1 for 16)
|
||||
FROM "Tenant" AS tenant
|
||||
WHERE app."tenantId" = tenant."id";
|
||||
|
||||
ALTER TABLE "SmsApplication" ALTER COLUMN "cmppEnterpriseCode" SET NOT NULL;
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
ALTER TABLE "EnterpriseBlacklist" ADD COLUMN "applicationId" TEXT;
|
||||
|
||||
UPDATE "EnterpriseBlacklist" AS blacklist
|
||||
SET "applicationId" = (
|
||||
SELECT "id"
|
||||
FROM "SmsApplication"
|
||||
WHERE "tenantId" = blacklist."tenantId"
|
||||
ORDER BY "createdAt" DESC
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
DELETE FROM "EnterpriseBlacklist" WHERE "applicationId" IS NULL;
|
||||
|
||||
DROP INDEX IF EXISTS "EnterpriseBlacklist_tenantId_phoneNumber_key";
|
||||
|
||||
ALTER TABLE "EnterpriseBlacklist" ALTER COLUMN "applicationId" SET NOT NULL;
|
||||
|
||||
CREATE UNIQUE INDEX "EnterpriseBlacklist_applicationId_phoneNumber_key" ON "EnterpriseBlacklist"("applicationId", "phoneNumber");
|
||||
|
||||
ALTER TABLE "EnterpriseBlacklist" ADD CONSTRAINT "EnterpriseBlacklist_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "User" ADD COLUMN "sessionVersion" INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -0,0 +1,29 @@
|
||||
CREATE TABLE "CmppDownstreamConnection" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"account" TEXT NOT NULL,
|
||||
"enterpriseCode" TEXT NOT NULL,
|
||||
"connectionId" TEXT NOT NULL,
|
||||
"remoteIp" TEXT,
|
||||
"protocol" TEXT,
|
||||
"status" TEXT NOT NULL DEFAULT 'connected',
|
||||
"connectedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"lastHeartbeatAt" TIMESTAMP(3),
|
||||
"lastSubmitAt" TIMESTAMP(3),
|
||||
"lastDeliverAt" TIMESTAMP(3),
|
||||
"disconnectedAt" TIMESTAMP(3),
|
||||
"lastError" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "CmppDownstreamConnection_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "CmppDownstreamConnection_connectionId_key" ON "CmppDownstreamConnection"("connectionId");
|
||||
CREATE INDEX "CmppDownstreamConnection_applicationId_status_updatedAt_idx" ON "CmppDownstreamConnection"("applicationId", "status", "updatedAt");
|
||||
CREATE INDEX "CmppDownstreamConnection_tenantId_status_updatedAt_idx" ON "CmppDownstreamConnection"("tenantId", "status", "updatedAt");
|
||||
CREATE INDEX "CmppDownstreamConnection_account_status_idx" ON "CmppDownstreamConnection"("account", "status");
|
||||
|
||||
ALTER TABLE "CmppDownstreamConnection" ADD CONSTRAINT "CmppDownstreamConnection_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "CmppDownstreamConnection" ADD CONSTRAINT "CmppDownstreamConnection_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,21 @@
|
||||
ALTER TABLE "SmsMessageRecord"
|
||||
ADD COLUMN "carrier" TEXT,
|
||||
ADD COLUMN "province" TEXT;
|
||||
|
||||
UPDATE "SmsMessageRecord" AS message
|
||||
SET "province" = segment."province"
|
||||
FROM "PhoneSegment" AS segment
|
||||
WHERE segment."prefix" = left(message."phoneNumber", 7);
|
||||
|
||||
UPDATE "SmsMessageRecord" AS message
|
||||
SET "carrier" = COALESCE((
|
||||
SELECT rule."carrier"
|
||||
FROM "PhoneCarrierRule" AS rule
|
||||
WHERE rule."status" = 'active'
|
||||
AND message."phoneNumber" ~ rule."pattern"
|
||||
ORDER BY rule."priority" ASC, rule."createdAt" ASC
|
||||
LIMIT 1
|
||||
), 'mobile');
|
||||
|
||||
CREATE INDEX "SmsMessageRecord_carrier_province_idx"
|
||||
ON "SmsMessageRecord"("carrier", "province");
|
||||
@@ -0,0 +1,21 @@
|
||||
ALTER TABLE "SmsSendTask"
|
||||
ADD COLUMN "sourceType" TEXT NOT NULL DEFAULT 'risk',
|
||||
ADD COLUMN "aggregationKey" TEXT,
|
||||
ADD COLUMN "contentHash" TEXT,
|
||||
ADD COLUMN "windowStartedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "windowEndsAt" TIMESTAMP(3);
|
||||
|
||||
ALTER TABLE "SmsMessageRecord"
|
||||
ADD COLUMN "signatureId" TEXT,
|
||||
ADD COLUMN "reviewTaskId" TEXT;
|
||||
|
||||
CREATE UNIQUE INDEX "SmsSendTask_aggregationKey_key" ON "SmsSendTask"("aggregationKey");
|
||||
CREATE INDEX "SmsMessageRecord_reviewTaskId_status_idx" ON "SmsMessageRecord"("reviewTaskId", "status");
|
||||
|
||||
ALTER TABLE "SmsMessageRecord"
|
||||
ADD CONSTRAINT "SmsMessageRecord_signatureId_fkey"
|
||||
FOREIGN KEY ("signatureId") REFERENCES "SmsSignature"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "SmsMessageRecord"
|
||||
ADD CONSTRAINT "SmsMessageRecord_reviewTaskId_fkey"
|
||||
FOREIGN KEY ("reviewTaskId") REFERENCES "SmsSendTask"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,33 @@
|
||||
ALTER TABLE "ChannelReportField"
|
||||
ADD COLUMN "drainageFieldId" TEXT,
|
||||
ADD COLUMN "reportType" TEXT NOT NULL DEFAULT 'both';
|
||||
|
||||
CREATE INDEX "ChannelReportField_drainageFieldId_reportType_idx"
|
||||
ON "ChannelReportField"("drainageFieldId", "reportType");
|
||||
|
||||
ALTER TABLE "ChannelReportField"
|
||||
ADD CONSTRAINT "ChannelReportField_drainageFieldId_fkey"
|
||||
FOREIGN KEY ("drainageFieldId") REFERENCES "DrainageField"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
CREATE TABLE "DrainageReportMaterial" (
|
||||
"id" TEXT NOT NULL,
|
||||
"signatureId" TEXT NOT NULL,
|
||||
"drainageItemId" TEXT NOT NULL,
|
||||
"channelId" TEXT NOT NULL,
|
||||
"fieldCode" TEXT NOT NULL,
|
||||
"fieldValue" TEXT,
|
||||
"fileObjectId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "DrainageReportMaterial_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "DrainageReportMaterial_signatureId_drainageItemId_channelId_fieldCode_key"
|
||||
ON "DrainageReportMaterial"("signatureId", "drainageItemId", "channelId", "fieldCode");
|
||||
CREATE INDEX "DrainageReportMaterial_channelId_fieldCode_idx"
|
||||
ON "DrainageReportMaterial"("channelId", "fieldCode");
|
||||
|
||||
ALTER TABLE "DrainageReportMaterial" ADD CONSTRAINT "DrainageReportMaterial_signatureId_fkey"
|
||||
FOREIGN KEY ("signatureId") REFERENCES "SmsSignature"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "DrainageReportMaterial" ADD CONSTRAINT "DrainageReportMaterial_channelId_fkey"
|
||||
FOREIGN KEY ("channelId") REFERENCES "SmsChannel"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,9 @@
|
||||
UPDATE "DrainageField"
|
||||
SET "fieldType" = 'string'
|
||||
WHERE "fieldType" NOT IN ('string', 'image', 'file');
|
||||
|
||||
UPDATE "ChannelReportField"
|
||||
SET "fieldType" = CASE
|
||||
WHEN "fieldType" IN ('string', 'image', 'file') THEN "fieldType"
|
||||
ELSE 'string'
|
||||
END;
|
||||
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE "ChannelSignatureReportTask"
|
||||
ADD COLUMN "reportType" TEXT NOT NULL DEFAULT 'signature',
|
||||
ADD COLUMN "drainageItemId" TEXT;
|
||||
|
||||
CREATE INDEX "ChannelSignatureReportTask_signatureId_drainageItemId_channelId_idx"
|
||||
ON "ChannelSignatureReportTask"("signatureId", "drainageItemId", "channelId");
|
||||
|
||||
CREATE INDEX "ChannelSignatureReportTask_reportType_status_idx"
|
||||
ON "ChannelSignatureReportTask"("reportType", "status");
|
||||
@@ -0,0 +1,45 @@
|
||||
INSERT INTO "ChannelSignatureReportTask" (
|
||||
"id", "tenantId", "signatureId", "channelId", "reportType", "drainageItemId", "status", "createdAt", "updatedAt"
|
||||
)
|
||||
SELECT
|
||||
'drainage_' || md5(random()::text || clock_timestamp()::text || material."id"),
|
||||
signature."tenantId",
|
||||
material."signatureId",
|
||||
material."channelId",
|
||||
'drainage',
|
||||
material."drainageItemId",
|
||||
'pending',
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM (
|
||||
SELECT DISTINCT ON ("signatureId", "drainageItemId", "channelId")
|
||||
"id", "signatureId", "drainageItemId", "channelId"
|
||||
FROM "DrainageReportMaterial"
|
||||
ORDER BY "signatureId", "drainageItemId", "channelId", "createdAt"
|
||||
) material
|
||||
JOIN "SmsSignature" signature ON signature."id" = material."signatureId"
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "ChannelSignatureReportTask" task
|
||||
WHERE task."signatureId" = material."signatureId"
|
||||
AND task."channelId" = material."channelId"
|
||||
AND task."reportType" = 'drainage'
|
||||
AND task."drainageItemId" = material."drainageItemId"
|
||||
);
|
||||
|
||||
INSERT INTO "ChannelSignatureReportRecord" (
|
||||
"id", "taskId", "channelId", "action", "statusAfter", "reason", "createdAt"
|
||||
)
|
||||
SELECT
|
||||
'drainage_record_' || md5(random()::text || clock_timestamp()::text || task."id"),
|
||||
task."id",
|
||||
task."channelId",
|
||||
'migration_backfill',
|
||||
task."status",
|
||||
'从已有引流报备材料回填任务',
|
||||
CURRENT_TIMESTAMP
|
||||
FROM "ChannelSignatureReportTask" task
|
||||
WHERE task."reportType" = 'drainage'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM "ChannelSignatureReportRecord" record WHERE record."taskId" = task."id"
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
CREATE UNIQUE INDEX "ChannelSignatureReportTask_target_key"
|
||||
ON "ChannelSignatureReportTask"(
|
||||
"signatureId",
|
||||
"channelId",
|
||||
"reportType",
|
||||
COALESCE("drainageItemId", '')
|
||||
);
|
||||
@@ -0,0 +1,78 @@
|
||||
CREATE TABLE "SmsDrainageInfo" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"signatureId" TEXT NOT NULL,
|
||||
"applicationId" TEXT,
|
||||
"siteName" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"remark" TEXT,
|
||||
"reportValues" JSONB,
|
||||
"auditStatus" TEXT NOT NULL DEFAULT 'pending',
|
||||
"rejectReason" TEXT,
|
||||
"submittedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"reviewedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "SmsDrainageInfo_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TEMP TABLE drainage_backfill_map AS
|
||||
SELECT
|
||||
signature."id" AS "signatureId",
|
||||
COALESCE(NULLIF(item.value->>'id', ''), item.ordinality::text) AS "oldId",
|
||||
'drain_' || md5(signature."id" || ':' || COALESCE(NULLIF(item.value->>'id', ''), item.ordinality::text)) AS "newId",
|
||||
signature."tenantId" AS "tenantId",
|
||||
signature."applicationId" AS "applicationId",
|
||||
item.value AS item
|
||||
FROM "SmsSignature" signature
|
||||
CROSS JOIN LATERAL jsonb_array_elements(COALESCE(signature."drainageInfo"->'links', '[]'::jsonb)) WITH ORDINALITY AS item(value, ordinality);
|
||||
|
||||
INSERT INTO "SmsDrainageInfo" (
|
||||
"id", "tenantId", "signatureId", "applicationId", "siteName", "url", "remark", "reportValues",
|
||||
"auditStatus", "submittedAt", "createdAt", "updatedAt"
|
||||
)
|
||||
SELECT
|
||||
"newId",
|
||||
"tenantId",
|
||||
"signatureId",
|
||||
"applicationId",
|
||||
COALESCE(NULLIF(item->>'siteName', ''), '未命名站点'),
|
||||
COALESCE(item->>'url', ''),
|
||||
NULLIF(item->>'remark', ''),
|
||||
COALESCE(item->'reportValues', '{}'::jsonb),
|
||||
CASE WHEN item->>'auditStatus' IN ('pending', 'approved', 'rejected') THEN item->>'auditStatus' ELSE 'pending' END,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM drainage_backfill_map;
|
||||
|
||||
UPDATE "ChannelSignatureReportTask" task
|
||||
SET "drainageItemId" = map."newId"
|
||||
FROM drainage_backfill_map map
|
||||
WHERE task."signatureId" = map."signatureId"
|
||||
AND task."reportType" = 'drainage'
|
||||
AND task."drainageItemId" = map."oldId";
|
||||
|
||||
UPDATE "DrainageReportMaterial" material
|
||||
SET "drainageItemId" = map."newId"
|
||||
FROM drainage_backfill_map map
|
||||
WHERE material."signatureId" = map."signatureId"
|
||||
AND material."drainageItemId" = map."oldId";
|
||||
|
||||
DROP TABLE drainage_backfill_map;
|
||||
|
||||
CREATE INDEX "SmsDrainageInfo_tenantId_auditStatus_updatedAt_idx"
|
||||
ON "SmsDrainageInfo"("tenantId", "auditStatus", "updatedAt");
|
||||
CREATE INDEX "SmsDrainageInfo_signatureId_auditStatus_idx"
|
||||
ON "SmsDrainageInfo"("signatureId", "auditStatus");
|
||||
CREATE INDEX "SmsDrainageInfo_applicationId_idx"
|
||||
ON "SmsDrainageInfo"("applicationId");
|
||||
|
||||
ALTER TABLE "SmsDrainageInfo" ADD CONSTRAINT "SmsDrainageInfo_tenantId_fkey"
|
||||
FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "SmsDrainageInfo" ADD CONSTRAINT "SmsDrainageInfo_signatureId_fkey"
|
||||
FOREIGN KEY ("signatureId") REFERENCES "SmsSignature"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "SmsDrainageInfo" ADD CONSTRAINT "SmsDrainageInfo_applicationId_fkey"
|
||||
FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "ChannelSignatureReportTask" ADD CONSTRAINT "ChannelSignatureReportTask_drainageItemId_fkey"
|
||||
FOREIGN KEY ("drainageItemId") REFERENCES "SmsDrainageInfo"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "ChannelSignatureReportRecord"
|
||||
ADD COLUMN "sourceEntry" TEXT NOT NULL DEFAULT 'system';
|
||||
@@ -0,0 +1,4 @@
|
||||
UPDATE "ChannelSignatureReportRecord"
|
||||
SET "sourceEntry" = 'legacy'
|
||||
WHERE "action" = 'manual_status_change'
|
||||
AND "sourceEntry" = 'system';
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
ALTER TABLE "SmsApplication"
|
||||
ADD COLUMN "downstreamReceiptRetryEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN "downstreamUplinkRetryEnabled" BOOLEAN NOT NULL DEFAULT true;
|
||||
|
||||
ALTER TABLE "CmppDownstreamDelivery"
|
||||
ADD COLUMN "retryEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN "sentAt" TIMESTAMP(3),
|
||||
ADD COLUMN "acknowledgedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "ackDeadlineAt" TIMESTAMP(3),
|
||||
ADD COLUMN "ackResult" INTEGER,
|
||||
ADD COLUMN "ackSequenceId" TEXT,
|
||||
ADD COLUMN "ackMessageId" TEXT,
|
||||
ADD COLUMN "connectionId" TEXT;
|
||||
|
||||
UPDATE "CmppDownstreamDelivery"
|
||||
SET
|
||||
"status" = 'unconfirmed',
|
||||
"sentAt" = "deliveredAt",
|
||||
"deliveredAt" = NULL,
|
||||
"lastError" = '历史记录仅确认 Gateway 已写出,未留存 CMPP_DELIVER_RESP'
|
||||
WHERE "status" = 'delivered';
|
||||
|
||||
UPDATE "CmppDownstreamDelivery"
|
||||
SET "retryEnabled" = CASE
|
||||
WHEN "deliveryType" = 'uplink' THEN app."downstreamUplinkRetryEnabled"
|
||||
ELSE app."downstreamReceiptRetryEnabled"
|
||||
END
|
||||
FROM "SmsApplication" app
|
||||
WHERE app.id = "CmppDownstreamDelivery"."applicationId";
|
||||
|
||||
CREATE INDEX "CmppDownstreamDelivery_status_ackDeadlineAt_idx"
|
||||
ON "CmppDownstreamDelivery"("status", "ackDeadlineAt");
|
||||
@@ -0,0 +1,11 @@
|
||||
ALTER TABLE "SmsMessageRecord"
|
||||
ADD COLUMN "cmppSubmitSequenceId" TEXT;
|
||||
|
||||
UPDATE "CmppDownstreamDelivery"
|
||||
SET
|
||||
"status" = 'unconfirmed',
|
||||
"deliveredAt" = NULL,
|
||||
"lastError" = 'CMPP_DELIVER_RESP Msg_Id=0,仅确认协议收包,未确认业务回执关联'
|
||||
WHERE "deliveryType" = 'receipt'
|
||||
AND "status" = 'delivered'
|
||||
AND COALESCE("ackMessageId", '0') = '0';
|
||||
@@ -0,0 +1,37 @@
|
||||
CREATE INDEX "OperationLog_createdAt_idx"
|
||||
ON "OperationLog"("createdAt");
|
||||
|
||||
CREATE INDEX "OperationLog_resource_createdAt_idx"
|
||||
ON "OperationLog"("resource", "createdAt");
|
||||
|
||||
CREATE TABLE "OperationLogArchive" (
|
||||
"originalId" TEXT NOT NULL,
|
||||
"tenantId" TEXT,
|
||||
"userId" TEXT,
|
||||
"action" TEXT NOT NULL,
|
||||
"resource" TEXT NOT NULL,
|
||||
"resourceId" TEXT,
|
||||
"ipAddress" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"detail" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL,
|
||||
"archiveMonth" TEXT NOT NULL,
|
||||
"archivedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "OperationLogArchive_pkey" PRIMARY KEY ("originalId")
|
||||
);
|
||||
|
||||
CREATE INDEX "OperationLogArchive_tenantId_createdAt_idx"
|
||||
ON "OperationLogArchive"("tenantId", "createdAt");
|
||||
|
||||
CREATE INDEX "OperationLogArchive_userId_createdAt_idx"
|
||||
ON "OperationLogArchive"("userId", "createdAt");
|
||||
|
||||
CREATE INDEX "OperationLogArchive_createdAt_idx"
|
||||
ON "OperationLogArchive"("createdAt");
|
||||
|
||||
CREATE INDEX "OperationLogArchive_resource_createdAt_idx"
|
||||
ON "OperationLogArchive"("resource", "createdAt");
|
||||
|
||||
CREATE INDEX "OperationLogArchive_archiveMonth_idx"
|
||||
ON "OperationLogArchive"("archiveMonth");
|
||||
@@ -0,0 +1,14 @@
|
||||
ALTER TABLE "RechargeOrder" DROP CONSTRAINT IF EXISTS "RechargeOrder_planId_fkey";
|
||||
|
||||
DROP TABLE IF EXISTS "BillingPlan";
|
||||
|
||||
ALTER TABLE "TenantAccount"
|
||||
DROP COLUMN "smsUnits",
|
||||
DROP COLUMN "creditCents";
|
||||
|
||||
ALTER TABLE "AccountTransaction"
|
||||
DROP COLUMN "smsUnits";
|
||||
|
||||
ALTER TABLE "RechargeOrder"
|
||||
DROP COLUMN "planId",
|
||||
DROP COLUMN "smsUnits";
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "TenantAccount"
|
||||
ADD COLUMN "creditCents" INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "CmppDownstreamDelivery"
|
||||
ADD COLUMN "manualRetryCount" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "lastRetriedAt" TIMESTAMP(3);
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
ALTER TABLE "SmsSubmitRecord"
|
||||
ADD COLUMN "costUnitPrice" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "costAmountCents" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
UPDATE "SmsSubmitRecord" submit
|
||||
SET
|
||||
"costUnitPrice" = channel."unitPrice",
|
||||
"costAmountCents" = channel."unitPrice" * message."billingUnits"
|
||||
FROM "SmsChannel" channel, "SmsMessageRecord" message
|
||||
WHERE submit."channelId" = channel.id
|
||||
AND submit."messageRecordId" = message.id;
|
||||
|
||||
CREATE TABLE "DailyReconciliationReport" (
|
||||
"id" TEXT NOT NULL,
|
||||
"reportDate" DATE NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"tenantName" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"applicationName" TEXT NOT NULL,
|
||||
"sentUnits" INTEGER NOT NULL DEFAULT 0,
|
||||
"successUnits" INTEGER NOT NULL DEFAULT 0,
|
||||
"generatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "DailyReconciliationReport_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "DailyProfitReport" (
|
||||
"id" TEXT NOT NULL,
|
||||
"reportDate" DATE NOT NULL,
|
||||
"dimensionType" TEXT NOT NULL,
|
||||
"dimensionId" TEXT NOT NULL,
|
||||
"dimensionName" TEXT NOT NULL,
|
||||
"tenantId" TEXT,
|
||||
"tenantName" TEXT,
|
||||
"applicationId" TEXT,
|
||||
"channelId" TEXT,
|
||||
"sentUnits" INTEGER NOT NULL DEFAULT 0,
|
||||
"successUnits" INTEGER NOT NULL DEFAULT 0,
|
||||
"revenueCents" INTEGER NOT NULL DEFAULT 0,
|
||||
"costCents" INTEGER NOT NULL DEFAULT 0,
|
||||
"profitCents" INTEGER NOT NULL DEFAULT 0,
|
||||
"profitRateBps" INTEGER NOT NULL DEFAULT 0,
|
||||
"generatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "DailyProfitReport_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "DailyReconciliationReport_reportDate_tenantId_applicationId_key"
|
||||
ON "DailyReconciliationReport"("reportDate", "tenantId", "applicationId");
|
||||
CREATE INDEX "DailyReconciliationReport_reportDate_idx" ON "DailyReconciliationReport"("reportDate");
|
||||
CREATE INDEX "DailyReconciliationReport_tenantId_reportDate_idx" ON "DailyReconciliationReport"("tenantId", "reportDate");
|
||||
CREATE INDEX "DailyReconciliationReport_applicationId_reportDate_idx" ON "DailyReconciliationReport"("applicationId", "reportDate");
|
||||
|
||||
CREATE UNIQUE INDEX "DailyProfitReport_reportDate_dimensionType_dimensionId_key"
|
||||
ON "DailyProfitReport"("reportDate", "dimensionType", "dimensionId");
|
||||
CREATE INDEX "DailyProfitReport_dimensionType_reportDate_idx" ON "DailyProfitReport"("dimensionType", "reportDate");
|
||||
CREATE INDEX "DailyProfitReport_tenantId_reportDate_idx" ON "DailyProfitReport"("tenantId", "reportDate");
|
||||
CREATE INDEX "DailyProfitReport_applicationId_reportDate_idx" ON "DailyProfitReport"("applicationId", "reportDate");
|
||||
CREATE INDEX "DailyProfitReport_channelId_reportDate_idx" ON "DailyProfitReport"("channelId", "reportDate");
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
-- Downstream connection rows represent current live sessions only.
|
||||
-- Remove historical disconnected/timeout rows left by the previous persistence model.
|
||||
DELETE FROM "CmppDownstreamConnection"
|
||||
WHERE "status" <> 'connected';
|
||||
@@ -0,0 +1,23 @@
|
||||
CREATE TABLE "CommonReportField" (
|
||||
"id" TEXT NOT NULL,
|
||||
"drainageFieldId" TEXT NOT NULL,
|
||||
"reportType" TEXT NOT NULL,
|
||||
"required" BOOLEAN NOT NULL DEFAULT false,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 100,
|
||||
"status" TEXT NOT NULL DEFAULT 'active',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "CommonReportField_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "CommonReportField_drainageFieldId_reportType_key"
|
||||
ON "CommonReportField"("drainageFieldId", "reportType");
|
||||
|
||||
CREATE INDEX "CommonReportField_reportType_status_sortOrder_idx"
|
||||
ON "CommonReportField"("reportType", "status", "sortOrder");
|
||||
|
||||
ALTER TABLE "CommonReportField"
|
||||
ADD CONSTRAINT "CommonReportField_drainageFieldId_fkey"
|
||||
FOREIGN KEY ("drainageFieldId") REFERENCES "DrainageField"("id")
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,65 @@
|
||||
ALTER TABLE "SmsMessageRecord" ADD COLUMN "drainageInfoId" TEXT;
|
||||
|
||||
ALTER TABLE "SmsMessageRecord"
|
||||
ADD CONSTRAINT "SmsMessageRecord_drainageInfoId_fkey"
|
||||
FOREIGN KEY ("drainageInfoId") REFERENCES "SmsDrainageInfo"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
CREATE INDEX "SmsMessageRecord_drainageInfoId_queuedAt_idx"
|
||||
ON "SmsMessageRecord"("drainageInfoId", "queuedAt");
|
||||
|
||||
WITH drainage_matches AS (
|
||||
SELECT
|
||||
message.id AS "messageRecordId",
|
||||
drainage.id AS "drainageInfoId",
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY message.id
|
||||
ORDER BY LENGTH(drainage.url) DESC, drainage."updatedAt" DESC, drainage.id
|
||||
) AS match_rank,
|
||||
COUNT(*) OVER (
|
||||
PARTITION BY message.id, LENGTH(drainage.url)
|
||||
) AS same_length_matches
|
||||
FROM "SmsMessageRecord" message
|
||||
JOIN "SmsDrainageInfo" drainage
|
||||
ON drainage."signatureId" = message."signatureId"
|
||||
AND drainage."auditStatus" = 'approved'
|
||||
AND LENGTH(BTRIM(drainage.url)) > 0
|
||||
AND POSITION(BTRIM(drainage.url) IN message.content) > 0
|
||||
WHERE message."drainageInfoId" IS NULL
|
||||
)
|
||||
UPDATE "SmsMessageRecord" message
|
||||
SET "drainageInfoId" = matched."drainageInfoId"
|
||||
FROM drainage_matches matched
|
||||
WHERE message.id = matched."messageRecordId"
|
||||
AND matched.match_rank = 1
|
||||
AND matched.same_length_matches = 1;
|
||||
|
||||
CREATE TABLE "DailyQualityReport" (
|
||||
"id" TEXT NOT NULL,
|
||||
"reportDate" DATE NOT NULL,
|
||||
"dimensionType" TEXT NOT NULL,
|
||||
"dimensionId" TEXT NOT NULL,
|
||||
"dimensionName" TEXT NOT NULL,
|
||||
"tenantId" TEXT,
|
||||
"tenantName" TEXT,
|
||||
"applicationId" TEXT,
|
||||
"channelId" TEXT,
|
||||
"signatureId" TEXT,
|
||||
"drainageInfoId" TEXT,
|
||||
"sentUnits" INTEGER NOT NULL DEFAULT 0,
|
||||
"successUnits" INTEGER NOT NULL DEFAULT 0,
|
||||
"successRateBps" INTEGER NOT NULL DEFAULT 0,
|
||||
"avgArrivalMs" INTEGER,
|
||||
"generatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "DailyQualityReport_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "DailyQualityReport_reportDate_dimensionType_dimensionId_key"
|
||||
ON "DailyQualityReport"("reportDate", "dimensionType", "dimensionId");
|
||||
CREATE INDEX "DailyQualityReport_dimensionType_reportDate_sentUnits_idx"
|
||||
ON "DailyQualityReport"("dimensionType", "reportDate", "sentUnits");
|
||||
CREATE INDEX "DailyQualityReport_tenantId_reportDate_idx" ON "DailyQualityReport"("tenantId", "reportDate");
|
||||
CREATE INDEX "DailyQualityReport_applicationId_reportDate_idx" ON "DailyQualityReport"("applicationId", "reportDate");
|
||||
CREATE INDEX "DailyQualityReport_channelId_reportDate_idx" ON "DailyQualityReport"("channelId", "reportDate");
|
||||
CREATE INDEX "DailyQualityReport_signatureId_reportDate_idx" ON "DailyQualityReport"("signatureId", "reportDate");
|
||||
CREATE INDEX "DailyQualityReport_drainageInfoId_reportDate_idx" ON "DailyQualityReport"("drainageInfoId", "reportDate");
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
ALTER TABLE "SmsApplication"
|
||||
ADD COLUMN "cmppApplicationExtension" TEXT,
|
||||
ADD COLUMN "cmppAccessNumberFillEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "cmppAccessNumberFillPrefix" TEXT,
|
||||
ADD COLUMN "cmppClientSrcId" TEXT;
|
||||
|
||||
CREATE UNIQUE INDEX "SmsApplication_cmppClientSrcId_key"
|
||||
ON "SmsApplication"("cmppClientSrcId");
|
||||
|
||||
ALTER TABLE "SmsMessageRecord"
|
||||
ADD COLUMN "clientSrcId" TEXT,
|
||||
ADD COLUMN "applicationExtension" TEXT;
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
ALTER TABLE "SmsSignature"
|
||||
ADD COLUMN "materialVersion" INTEGER NOT NULL DEFAULT 1,
|
||||
ADD COLUMN "pendingReport" BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN "reportChangedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
ALTER TABLE "SmsDrainageInfo"
|
||||
ADD COLUMN "materialVersion" INTEGER NOT NULL DEFAULT 1,
|
||||
ADD COLUMN "pendingReport" BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN "reportChangedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
ALTER TABLE "ChannelReportField"
|
||||
ADD COLUMN "exportName" TEXT,
|
||||
ADD COLUMN "columnWidth" INTEGER NOT NULL DEFAULT 18,
|
||||
ADD COLUMN "imageWidth" INTEGER NOT NULL DEFAULT 120,
|
||||
ADD COLUMN "imageHeight" INTEGER NOT NULL DEFAULT 80,
|
||||
ADD COLUMN "defaultValue" TEXT,
|
||||
ADD COLUMN "transform" TEXT;
|
||||
|
||||
ALTER TABLE "ReportExportFile"
|
||||
ALTER COLUMN "taskId" DROP NOT NULL,
|
||||
ADD COLUMN "batchId" TEXT,
|
||||
ADD COLUMN "channelId" TEXT;
|
||||
|
||||
CREATE TABLE "ReportMaterialBatch" (
|
||||
"id" TEXT NOT NULL,
|
||||
"batchNo" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'generating',
|
||||
"createdById" TEXT,
|
||||
"selectedCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"channelCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"fileCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"errorMessage" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"completedAt" TIMESTAMP(3),
|
||||
CONSTRAINT "ReportMaterialBatch_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "ReportMaterialBatchItem" (
|
||||
"id" TEXT NOT NULL,
|
||||
"batchId" TEXT NOT NULL,
|
||||
"signatureId" TEXT NOT NULL,
|
||||
"drainageItemId" TEXT,
|
||||
"reportType" TEXT NOT NULL,
|
||||
"materialVersion" INTEGER NOT NULL,
|
||||
"snapshot" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "ReportMaterialBatchItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "ReportExportFileItem" (
|
||||
"id" TEXT NOT NULL,
|
||||
"exportFileId" TEXT NOT NULL,
|
||||
"batchItemId" TEXT NOT NULL,
|
||||
"taskId" TEXT NOT NULL,
|
||||
"rowNumber" INTEGER NOT NULL,
|
||||
CONSTRAINT "ReportExportFileItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "ReportMaterialImportProfile" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"reportType" TEXT NOT NULL,
|
||||
"tenantId" TEXT,
|
||||
"applicationId" TEXT,
|
||||
"sheetName" TEXT,
|
||||
"headerRowCount" INTEGER NOT NULL DEFAULT 1,
|
||||
"dataStartRow" INTEGER NOT NULL DEFAULT 2,
|
||||
"status" TEXT NOT NULL DEFAULT 'active',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "ReportMaterialImportProfile_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "ReportMaterialImportProfileColumn" (
|
||||
"id" TEXT NOT NULL,
|
||||
"profileId" TEXT NOT NULL,
|
||||
"sourceHeader" TEXT NOT NULL,
|
||||
"sourceHeaderPath" TEXT,
|
||||
"sourceColumnIndex" INTEGER NOT NULL,
|
||||
"targetFieldCode" TEXT NOT NULL,
|
||||
"targetKind" TEXT NOT NULL DEFAULT 'dynamic',
|
||||
"fieldType" TEXT NOT NULL DEFAULT 'string',
|
||||
"required" BOOLEAN NOT NULL DEFAULT false,
|
||||
"transform" TEXT,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 100,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "ReportMaterialImportProfileColumn_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "ReportMaterialImportBatch" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"applicationId" TEXT,
|
||||
"profileId" TEXT,
|
||||
"fileObjectId" TEXT NOT NULL,
|
||||
"fileName" TEXT NOT NULL,
|
||||
"reportType" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'analyzed',
|
||||
"sheetName" TEXT NOT NULL,
|
||||
"headerRowCount" INTEGER NOT NULL DEFAULT 1,
|
||||
"dataStartRow" INTEGER NOT NULL DEFAULT 2,
|
||||
"mapping" JSONB NOT NULL,
|
||||
"preview" JSONB,
|
||||
"result" JSONB,
|
||||
"rowCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"successCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"failedCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"completedAt" TIMESTAMP(3),
|
||||
CONSTRAINT "ReportMaterialImportBatch_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "ReportMaterialBatch_batchNo_key" ON "ReportMaterialBatch"("batchNo");
|
||||
CREATE INDEX "ReportMaterialBatch_status_createdAt_idx" ON "ReportMaterialBatch"("status", "createdAt");
|
||||
CREATE INDEX "ReportMaterialBatchItem_batchId_reportType_idx" ON "ReportMaterialBatchItem"("batchId", "reportType");
|
||||
CREATE INDEX "ReportMaterialBatchItem_signatureId_drainageItemId_idx" ON "ReportMaterialBatchItem"("signatureId", "drainageItemId");
|
||||
CREATE UNIQUE INDEX "ReportExportFileItem_exportFileId_batchItemId_key" ON "ReportExportFileItem"("exportFileId", "batchItemId");
|
||||
CREATE INDEX "ReportExportFileItem_taskId_idx" ON "ReportExportFileItem"("taskId");
|
||||
CREATE INDEX "ReportExportFile_batchId_channelId_idx" ON "ReportExportFile"("batchId", "channelId");
|
||||
CREATE INDEX "ReportMaterialImportProfile_reportType_status_idx" ON "ReportMaterialImportProfile"("reportType", "status");
|
||||
CREATE INDEX "ReportMaterialImportProfile_tenantId_applicationId_idx" ON "ReportMaterialImportProfile"("tenantId", "applicationId");
|
||||
CREATE UNIQUE INDEX "ReportMaterialImportProfileColumn_profileId_sourceColumnIndex_key" ON "ReportMaterialImportProfileColumn"("profileId", "sourceColumnIndex");
|
||||
CREATE INDEX "ReportMaterialImportProfileColumn_profileId_sortOrder_idx" ON "ReportMaterialImportProfileColumn"("profileId", "sortOrder");
|
||||
CREATE INDEX "ReportMaterialImportBatch_tenantId_createdAt_idx" ON "ReportMaterialImportBatch"("tenantId", "createdAt");
|
||||
CREATE INDEX "ReportMaterialImportBatch_status_createdAt_idx" ON "ReportMaterialImportBatch"("status", "createdAt");
|
||||
|
||||
ALTER TABLE "ReportExportFile" ADD CONSTRAINT "ReportExportFile_batchId_fkey" FOREIGN KEY ("batchId") REFERENCES "ReportMaterialBatch"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "ReportMaterialBatchItem" ADD CONSTRAINT "ReportMaterialBatchItem_batchId_fkey" FOREIGN KEY ("batchId") REFERENCES "ReportMaterialBatch"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "ReportMaterialBatchItem" ADD CONSTRAINT "ReportMaterialBatchItem_signatureId_fkey" FOREIGN KEY ("signatureId") REFERENCES "SmsSignature"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "ReportMaterialBatchItem" ADD CONSTRAINT "ReportMaterialBatchItem_drainageItemId_fkey" FOREIGN KEY ("drainageItemId") REFERENCES "SmsDrainageInfo"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "ReportExportFileItem" ADD CONSTRAINT "ReportExportFileItem_exportFileId_fkey" FOREIGN KEY ("exportFileId") REFERENCES "ReportExportFile"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "ReportExportFileItem" ADD CONSTRAINT "ReportExportFileItem_batchItemId_fkey" FOREIGN KEY ("batchItemId") REFERENCES "ReportMaterialBatchItem"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "ReportExportFileItem" ADD CONSTRAINT "ReportExportFileItem_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "ChannelSignatureReportTask"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "ReportMaterialImportProfileColumn" ADD CONSTRAINT "ReportMaterialImportProfileColumn_profileId_fkey" FOREIGN KEY ("profileId") REFERENCES "ReportMaterialImportProfile"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
DROP INDEX IF EXISTS "ChannelReportField_channelId_code_key";
|
||||
|
||||
INSERT INTO "ChannelReportField" (
|
||||
"id", "channelId", "drainageFieldId", "reportType", "code", "name", "exportName", "fieldType",
|
||||
"required", "description", "sortOrder", "columnWidth", "imageWidth", "imageHeight", "defaultValue",
|
||||
"transform", "status", "createdAt", "updatedAt"
|
||||
)
|
||||
SELECT
|
||||
"id" || '_drainage', "channelId", "drainageFieldId", 'drainage', "code", "name", "exportName", "fieldType",
|
||||
"required", "description", "sortOrder", "columnWidth", "imageWidth", "imageHeight", "defaultValue",
|
||||
"transform", "status", "createdAt", "updatedAt"
|
||||
FROM "ChannelReportField"
|
||||
WHERE "reportType" = 'both';
|
||||
|
||||
UPDATE "ChannelReportField" SET "reportType" = 'signature' WHERE "reportType" = 'both';
|
||||
|
||||
CREATE UNIQUE INDEX "ChannelReportField_channelId_code_reportType_key"
|
||||
ON "ChannelReportField"("channelId", "code", "reportType");
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
-- Existing rows predate the pending-material workflow and must not be treated as newly changed.
|
||||
-- New rows continue to use the schema default (true), while later edits explicitly set true.
|
||||
UPDATE "SmsSignature" SET "pendingReport" = false;
|
||||
UPDATE "SmsDrainageInfo" SET "pendingReport" = false;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "SmsChannelGroupItem" DROP COLUMN "rateLimitPerSecond";
|
||||
@@ -0,0 +1,165 @@
|
||||
CREATE TABLE "SmsApplicationHttpConfig" (
|
||||
"id" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"sendEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"messageQueryEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"receiptWebhookEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"uplinkWebhookEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"uplinkQueryEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"credentialSelfServiceEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"qpsLimit" INTEGER NOT NULL DEFAULT 10,
|
||||
"timestampToleranceSeconds" INTEGER NOT NULL DEFAULT 300,
|
||||
"maxCredentialCount" INTEGER NOT NULL DEFAULT 2,
|
||||
"uplinkRetentionDays" INTEGER NOT NULL DEFAULT 90,
|
||||
"maxQueryRangeDays" INTEGER NOT NULL DEFAULT 31,
|
||||
"maxPageSize" INTEGER NOT NULL DEFAULT 100,
|
||||
"receiptDeliveryMode" TEXT NOT NULL DEFAULT 'cmpp',
|
||||
"uplinkDeliveryMode" TEXT NOT NULL DEFAULT 'cmpp',
|
||||
"webhookRetryEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"webhookMaxAttempts" INTEGER NOT NULL DEFAULT 7,
|
||||
"webhookTimeoutSeconds" INTEGER NOT NULL DEFAULT 10,
|
||||
"requireHttps" BOOLEAN NOT NULL DEFAULT true,
|
||||
"allowClientManualRetry" BOOLEAN NOT NULL DEFAULT true,
|
||||
"allowClientTest" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "SmsApplicationHttpConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "SmsApplicationHttpIpAllowlist" (
|
||||
"id" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"ipCidr" TEXT NOT NULL,
|
||||
"remark" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "SmsApplicationHttpIpAllowlist_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "HttpApiCredential" (
|
||||
"id" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"accessKey" TEXT NOT NULL,
|
||||
"secretEncrypted" TEXT NOT NULL,
|
||||
"secretLast4" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'active',
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"lastUsedAt" TIMESTAMP(3),
|
||||
"lastUsedIp" TEXT,
|
||||
"createdById" TEXT,
|
||||
"revokedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "HttpApiCredential_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "OpenApiRequest" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"credentialId" TEXT NOT NULL,
|
||||
"requestId" TEXT NOT NULL,
|
||||
"idempotencyKey" TEXT NOT NULL,
|
||||
"bodyHash" TEXT NOT NULL,
|
||||
"clientMessageId" TEXT,
|
||||
"messageRecordId" TEXT,
|
||||
"httpStatus" INTEGER,
|
||||
"businessCode" TEXT,
|
||||
"responseBody" JSONB,
|
||||
"sourceIp" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"durationMs" INTEGER,
|
||||
"status" TEXT NOT NULL DEFAULT 'processing',
|
||||
"completedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "OpenApiRequest_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "HttpWebhookEndpoint" (
|
||||
"id" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"eventType" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"secretEncrypted" TEXT NOT NULL,
|
||||
"secretLast4" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'active',
|
||||
"lastTestAt" TIMESTAMP(3),
|
||||
"lastTestStatus" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "HttpWebhookEndpoint_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "HttpWebhookEvent" (
|
||||
"id" TEXT NOT NULL,
|
||||
"eventId" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"eventType" TEXT NOT NULL,
|
||||
"messageRecordId" TEXT,
|
||||
"messageId" TEXT,
|
||||
"uplinkMessageId" TEXT,
|
||||
"payload" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "HttpWebhookEvent_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "HttpWebhookDelivery" (
|
||||
"id" TEXT NOT NULL,
|
||||
"eventId" TEXT NOT NULL,
|
||||
"endpointId" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'pending',
|
||||
"attemptCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"nextRetryAt" TIMESTAMP(3),
|
||||
"lastHttpStatus" INTEGER,
|
||||
"lastError" TEXT,
|
||||
"deliveredAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "HttpWebhookDelivery_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "HttpWebhookAttempt" (
|
||||
"id" TEXT NOT NULL,
|
||||
"deliveryId" TEXT NOT NULL,
|
||||
"attemptNo" INTEGER NOT NULL,
|
||||
"requestHeaders" JSONB,
|
||||
"responseStatus" INTEGER,
|
||||
"responseSummary" TEXT,
|
||||
"errorMessage" TEXT,
|
||||
"durationMs" INTEGER,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "HttpWebhookAttempt_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
ALTER TABLE "SmsMessageRecord" ADD COLUMN "clientMessageId" TEXT;
|
||||
|
||||
CREATE UNIQUE INDEX "SmsApplicationHttpConfig_applicationId_key" ON "SmsApplicationHttpConfig"("applicationId");
|
||||
CREATE UNIQUE INDEX "SmsApplicationHttpIpAllowlist_applicationId_ipCidr_key" ON "SmsApplicationHttpIpAllowlist"("applicationId", "ipCidr");
|
||||
CREATE UNIQUE INDEX "HttpApiCredential_accessKey_key" ON "HttpApiCredential"("accessKey");
|
||||
CREATE INDEX "HttpApiCredential_applicationId_status_idx" ON "HttpApiCredential"("applicationId", "status");
|
||||
CREATE UNIQUE INDEX "OpenApiRequest_requestId_key" ON "OpenApiRequest"("requestId");
|
||||
CREATE UNIQUE INDEX "OpenApiRequest_applicationId_idempotencyKey_key" ON "OpenApiRequest"("applicationId", "idempotencyKey");
|
||||
CREATE INDEX "OpenApiRequest_applicationId_createdAt_idx" ON "OpenApiRequest"("applicationId", "createdAt");
|
||||
CREATE UNIQUE INDEX "HttpWebhookEndpoint_applicationId_eventType_key" ON "HttpWebhookEndpoint"("applicationId", "eventType");
|
||||
CREATE UNIQUE INDEX "HttpWebhookEvent_eventId_key" ON "HttpWebhookEvent"("eventId");
|
||||
CREATE INDEX "HttpWebhookEvent_applicationId_createdAt_idx" ON "HttpWebhookEvent"("applicationId", "createdAt");
|
||||
CREATE UNIQUE INDEX "HttpWebhookDelivery_eventId_endpointId_key" ON "HttpWebhookDelivery"("eventId", "endpointId");
|
||||
CREATE INDEX "HttpWebhookDelivery_status_nextRetryAt_idx" ON "HttpWebhookDelivery"("status", "nextRetryAt");
|
||||
CREATE UNIQUE INDEX "HttpWebhookAttempt_deliveryId_attemptNo_key" ON "HttpWebhookAttempt"("deliveryId", "attemptNo");
|
||||
CREATE UNIQUE INDEX "SmsMessageRecord_applicationId_clientMessageId_key" ON "SmsMessageRecord"("applicationId", "clientMessageId");
|
||||
|
||||
ALTER TABLE "SmsApplicationHttpConfig" ADD CONSTRAINT "SmsApplicationHttpConfig_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "SmsApplicationHttpIpAllowlist" ADD CONSTRAINT "SmsApplicationHttpIpAllowlist_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "HttpApiCredential" ADD CONSTRAINT "HttpApiCredential_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "OpenApiRequest" ADD CONSTRAINT "OpenApiRequest_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "OpenApiRequest" ADD CONSTRAINT "OpenApiRequest_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "OpenApiRequest" ADD CONSTRAINT "OpenApiRequest_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "HttpApiCredential"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "HttpWebhookEndpoint" ADD CONSTRAINT "HttpWebhookEndpoint_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "HttpWebhookEvent" ADD CONSTRAINT "HttpWebhookEvent_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "HttpWebhookEvent" ADD CONSTRAINT "HttpWebhookEvent_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "HttpWebhookDelivery" ADD CONSTRAINT "HttpWebhookDelivery_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "HttpWebhookEvent"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "HttpWebhookDelivery" ADD CONSTRAINT "HttpWebhookDelivery_endpointId_fkey" FOREIGN KEY ("endpointId") REFERENCES "HttpWebhookEndpoint"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "HttpWebhookAttempt" ADD CONSTRAINT "HttpWebhookAttempt_deliveryId_fkey" FOREIGN KEY ("deliveryId") REFERENCES "HttpWebhookDelivery"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
-- Monetary values were historically stored as integer cents. The platform now
|
||||
-- stores integer units of 0.0001 yuan. Convert existing values by 100 while
|
||||
-- widening columns to bigint so balances and report aggregates do not inherit
|
||||
-- PostgreSQL integer's ~214,748 yuan ceiling at the new scale.
|
||||
|
||||
ALTER TABLE "TenantAccount"
|
||||
ALTER COLUMN "balanceCents" TYPE BIGINT USING ("balanceCents"::BIGINT * 100),
|
||||
ALTER COLUMN "creditCents" TYPE BIGINT USING ("creditCents"::BIGINT * 100);
|
||||
|
||||
ALTER TABLE "AccountTransaction"
|
||||
ALTER COLUMN "amountCents" TYPE BIGINT USING ("amountCents"::BIGINT * 100),
|
||||
ALTER COLUMN "balanceAfter" TYPE BIGINT USING ("balanceAfter"::BIGINT * 100);
|
||||
|
||||
ALTER TABLE "BillingRule"
|
||||
ALTER COLUMN "unitPrice" TYPE BIGINT USING ("unitPrice"::BIGINT * 100);
|
||||
|
||||
ALTER TABLE "RechargeOrder"
|
||||
ALTER COLUMN "amountCents" TYPE BIGINT USING ("amountCents"::BIGINT * 100);
|
||||
|
||||
ALTER TABLE "SmsBillingRecord"
|
||||
ALTER COLUMN "unitPrice" TYPE BIGINT USING ("unitPrice"::BIGINT * 100),
|
||||
ALTER COLUMN "amountCents" TYPE BIGINT USING ("amountCents"::BIGINT * 100);
|
||||
|
||||
ALTER TABLE "SmsApplication"
|
||||
ALTER COLUMN "customerUnitPrice" TYPE BIGINT USING ("customerUnitPrice"::BIGINT * 100);
|
||||
|
||||
ALTER TABLE "SmsChannel"
|
||||
ALTER COLUMN "unitPrice" TYPE BIGINT USING ("unitPrice"::BIGINT * 100);
|
||||
|
||||
ALTER TABLE "SmsMessageRecord"
|
||||
ALTER COLUMN "unitPrice" TYPE BIGINT USING ("unitPrice"::BIGINT * 100),
|
||||
ALTER COLUMN "amountCents" TYPE BIGINT USING ("amountCents"::BIGINT * 100);
|
||||
|
||||
ALTER TABLE "SmsSubmitRecord"
|
||||
ALTER COLUMN "costUnitPrice" TYPE BIGINT USING ("costUnitPrice"::BIGINT * 100),
|
||||
ALTER COLUMN "costAmountCents" TYPE BIGINT USING ("costAmountCents"::BIGINT * 100);
|
||||
|
||||
ALTER TABLE "DailyProfitReport"
|
||||
ALTER COLUMN "revenueCents" TYPE BIGINT USING ("revenueCents"::BIGINT * 100),
|
||||
ALTER COLUMN "costCents" TYPE BIGINT USING ("costCents"::BIGINT * 100),
|
||||
ALTER COLUMN "profitCents" TYPE BIGINT USING ("profitCents"::BIGINT * 100);
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "SmsMessageRecord"
|
||||
ADD COLUMN "cmppSubmitGroupMessageId" TEXT;
|
||||
@@ -0,0 +1,26 @@
|
||||
ALTER TABLE "SmsMessageRecord"
|
||||
ADD COLUMN "receiptRawStatus" TEXT;
|
||||
|
||||
ALTER TABLE "SmsReceiptRecord"
|
||||
ADD COLUMN "receiptKey" TEXT,
|
||||
ADD COLUMN "errorMessage" TEXT;
|
||||
|
||||
UPDATE "SmsReceiptRecord"
|
||||
SET "receiptKey" = MD5(
|
||||
COALESCE("channelId", '') || E'\u0001' ||
|
||||
"gatewayMessageId" || E'\u0001' ||
|
||||
"receiptStatus" || E'\u0001' ||
|
||||
"rawStatus"
|
||||
) || MD5("id");
|
||||
|
||||
ALTER TABLE "SmsReceiptRecord"
|
||||
ALTER COLUMN "receiptKey" SET NOT NULL;
|
||||
|
||||
CREATE UNIQUE INDEX "SmsReceiptRecord_receiptKey_key"
|
||||
ON "SmsReceiptRecord"("receiptKey");
|
||||
|
||||
CREATE INDEX "SmsSubmitRecord_channelId_gatewayMessageId_idx"
|
||||
ON "SmsSubmitRecord"("channelId", "gatewayMessageId");
|
||||
|
||||
CREATE INDEX "SmsReceiptRecord_channelId_gatewayMessageId_idx"
|
||||
ON "SmsReceiptRecord"("channelId", "gatewayMessageId");
|
||||
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE "DailyReconciliationReport"
|
||||
ADD COLUMN "failedUnits" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE "DailyProfitReport"
|
||||
ADD COLUMN "failedUnits" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "refundCents" BIGINT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE "DailyQualityReport"
|
||||
ADD COLUMN "failedUnits" INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -0,0 +1,11 @@
|
||||
ALTER TABLE "SmsReceiptRecord"
|
||||
ADD COLUMN "phoneNumber" TEXT;
|
||||
|
||||
UPDATE "SmsReceiptRecord" receipt
|
||||
SET "phoneNumber" = message."phoneNumber"
|
||||
FROM "SmsMessageRecord" message
|
||||
WHERE receipt."messageRecordId" = message.id
|
||||
AND receipt."phoneNumber" IS NULL;
|
||||
|
||||
CREATE INDEX "SmsReceiptRecord_channelId_gatewayMessageId_phoneNumber_idx"
|
||||
ON "SmsReceiptRecord"("channelId", "gatewayMessageId", "phoneNumber");
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
-- Historical receipts created before channel-aware matching could be attached to
|
||||
-- the right message but retain the wrong channel when two channels shared the
|
||||
-- same supplier account. Only repair rows that have exactly one submit record
|
||||
-- for the same message, upstream message id and destination phone number.
|
||||
WITH unique_receipt_submit AS (
|
||||
SELECT
|
||||
receipt.id AS receipt_id,
|
||||
MIN(submit."channelId") AS correct_channel_id
|
||||
FROM "SmsReceiptRecord" receipt
|
||||
JOIN "SmsMessageRecord" message
|
||||
ON message.id = receipt."messageRecordId"
|
||||
JOIN "SmsSubmitRecord" submit
|
||||
ON submit."messageRecordId" = message.id
|
||||
AND submit."gatewayMessageId" = receipt."gatewayMessageId"
|
||||
WHERE receipt."receiptStatus" = 'delivered'
|
||||
AND receipt."phoneNumber" = message."phoneNumber"
|
||||
GROUP BY receipt.id
|
||||
HAVING COUNT(submit.id) = 1
|
||||
)
|
||||
UPDATE "SmsReceiptRecord" receipt
|
||||
SET "channelId" = matched.correct_channel_id
|
||||
FROM unique_receipt_submit matched
|
||||
WHERE receipt.id = matched.receipt_id
|
||||
AND receipt."channelId" IS DISTINCT FROM matched.correct_channel_id;
|
||||
|
||||
-- Aggregate the corrected successful receipt onto the message master record.
|
||||
-- DISTINCT ON chooses the first successful receipt for segmented messages while
|
||||
-- preserving the original submit/gateway identity of the current attempt.
|
||||
WITH delivered_match AS (
|
||||
SELECT DISTINCT ON (message.id)
|
||||
message.id AS message_record_id,
|
||||
submit."channelId" AS channel_id,
|
||||
receipt."gatewayMessageId" AS gateway_message_id,
|
||||
receipt."rawStatus" AS raw_status,
|
||||
receipt."errorCode" AS error_code,
|
||||
receipt."errorMessage" AS error_message,
|
||||
receipt."deliveredAt" AS delivered_at
|
||||
FROM "SmsMessageRecord" message
|
||||
JOIN "SmsReceiptRecord" receipt
|
||||
ON receipt."messageRecordId" = message.id
|
||||
AND receipt."receiptStatus" = 'delivered'
|
||||
AND receipt."phoneNumber" = message."phoneNumber"
|
||||
JOIN "SmsSubmitRecord" submit
|
||||
ON submit."messageRecordId" = message.id
|
||||
AND submit."gatewayMessageId" = receipt."gatewayMessageId"
|
||||
AND submit."channelId" = receipt."channelId"
|
||||
WHERE message."gatewayMessageId" = receipt."gatewayMessageId"
|
||||
ORDER BY message.id, receipt."deliveredAt" ASC, receipt.id ASC
|
||||
)
|
||||
UPDATE "SmsMessageRecord" message
|
||||
SET
|
||||
status = 'delivered',
|
||||
"receiptStatus" = 'delivered',
|
||||
"receiptRawStatus" = matched.raw_status,
|
||||
"channelId" = matched.channel_id,
|
||||
"gatewayMessageId" = matched.gateway_message_id,
|
||||
"errorCode" = matched.error_code,
|
||||
"errorMessage" = matched.error_message,
|
||||
"deliveredAt" = matched.delivered_at,
|
||||
"timeoutAt" = NULL,
|
||||
"updatedAt" = CURRENT_TIMESTAMP
|
||||
FROM delivered_match matched
|
||||
WHERE message.id = matched.message_record_id
|
||||
AND (
|
||||
message.status IS DISTINCT FROM 'delivered'
|
||||
OR message."receiptStatus" IS DISTINCT FROM 'delivered'
|
||||
OR message."deliveredAt" IS DISTINCT FROM matched.delivered_at
|
||||
OR message."channelId" IS DISTINCT FROM matched.channel_id
|
||||
);
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
-- Enforce a real per-application daily send quota and make 100,000 the platform default.
|
||||
UPDATE "SmsApplication"
|
||||
SET "dailyLimit" = 100000
|
||||
WHERE "dailyLimit" IS NULL;
|
||||
|
||||
ALTER TABLE "SmsApplication"
|
||||
ALTER COLUMN "dailyLimit" SET DEFAULT 100000,
|
||||
ALTER COLUMN "dailyLimit" SET NOT NULL;
|
||||
|
||||
CREATE TABLE "SmsApplicationDailyUsage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"usageDate" DATE NOT NULL,
|
||||
"usedCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "SmsApplicationDailyUsage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "SmsApplicationDailyUsage_applicationId_usageDate_key"
|
||||
ON "SmsApplicationDailyUsage"("applicationId", "usageDate");
|
||||
CREATE INDEX "SmsApplicationDailyUsage_usageDate_idx"
|
||||
ON "SmsApplicationDailyUsage"("usageDate");
|
||||
ALTER TABLE "SmsApplicationDailyUsage"
|
||||
ADD CONSTRAINT "SmsApplicationDailyUsage_applicationId_fkey"
|
||||
FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- Preserve sends already accepted earlier on the deployment day, so deploying at
|
||||
-- midday cannot grant an extra full quota. The business day is Asia/Shanghai.
|
||||
INSERT INTO "SmsApplicationDailyUsage" (
|
||||
"id", "applicationId", "usageDate", "usedCount", "createdAt", "updatedAt"
|
||||
)
|
||||
SELECT
|
||||
md5(message."applicationId" || ':' || ((CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Shanghai')::date)::text),
|
||||
message."applicationId",
|
||||
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Shanghai')::date,
|
||||
COUNT(*)::integer,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM "SmsMessageRecord" message
|
||||
WHERE message."applicationId" IS NOT NULL
|
||||
AND message."queuedAt" >= ((CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Shanghai')::date::timestamp AT TIME ZONE 'Asia/Shanghai')
|
||||
AND message."queuedAt" < (((CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Shanghai')::date + 1)::timestamp AT TIME ZONE 'Asia/Shanghai')
|
||||
GROUP BY message."applicationId";
|
||||
|
||||
-- HTTP applications created before the UI default fix retained CMPP delivery modes.
|
||||
-- Existing enabled webhook capabilities are migrated to the HTTP transport they expose.
|
||||
UPDATE "SmsApplicationHttpConfig"
|
||||
SET "receiptDeliveryMode" = 'http'
|
||||
WHERE "enabled" = TRUE
|
||||
AND "receiptWebhookEnabled" = TRUE
|
||||
AND "receiptDeliveryMode" = 'cmpp';
|
||||
|
||||
UPDATE "SmsApplicationHttpConfig"
|
||||
SET "uplinkDeliveryMode" = 'http'
|
||||
WHERE "enabled" = TRUE
|
||||
AND "uplinkWebhookEnabled" = TRUE
|
||||
AND "uplinkDeliveryMode" = 'cmpp';
|
||||
|
||||
ALTER TABLE "SmsApplicationHttpConfig"
|
||||
ALTER COLUMN "sendEnabled" SET DEFAULT TRUE,
|
||||
ALTER COLUMN "messageQueryEnabled" SET DEFAULT TRUE,
|
||||
ALTER COLUMN "receiptWebhookEnabled" SET DEFAULT TRUE,
|
||||
ALTER COLUMN "uplinkWebhookEnabled" SET DEFAULT TRUE,
|
||||
ALTER COLUMN "uplinkQueryEnabled" SET DEFAULT TRUE,
|
||||
ALTER COLUMN "credentialSelfServiceEnabled" SET DEFAULT TRUE,
|
||||
ALTER COLUMN "receiptDeliveryMode" SET DEFAULT 'http',
|
||||
ALTER COLUMN "uplinkDeliveryMode" SET DEFAULT 'http';
|
||||
|
||||
-- Rollback guidance:
|
||||
-- Drop SmsApplicationDailyUsage and its FK/indexes, restore SmsApplication.dailyLimit
|
||||
-- to nullable/no-default, and restore HTTP column defaults to FALSE/'cmpp'. Data migrated
|
||||
-- from cmpp to http is intentionally not auto-reversed because explicit edits after upgrade
|
||||
-- cannot be distinguished safely.
|
||||
@@ -0,0 +1,20 @@
|
||||
-- SMS signature names are canonical business identifiers and must contain
|
||||
-- exactly one complete pair of Chinese black brackets: 【signature】.
|
||||
WITH normalized AS (
|
||||
SELECT
|
||||
id,
|
||||
btrim(
|
||||
regexp_replace(
|
||||
regexp_replace(btrim(name), '^[【\[]+', ''),
|
||||
'[】\]]+$',
|
||||
''
|
||||
)
|
||||
) AS inner_name
|
||||
FROM "SmsSignature"
|
||||
)
|
||||
UPDATE "SmsSignature" AS signature
|
||||
SET name = '【' || normalized.inner_name || '】'
|
||||
FROM normalized
|
||||
WHERE signature.id = normalized.id
|
||||
AND normalized.inner_name <> ''
|
||||
AND signature.name IS DISTINCT FROM '【' || normalized.inner_name || '】';
|
||||
@@ -0,0 +1,19 @@
|
||||
-- The previous bracket-normalization migration ran against a legacy SQL_ASCII
|
||||
-- database. PostgreSQL treated the UTF-8 bracket regexp as individual bytes and
|
||||
-- removed the trailing 0x91 byte from the final Chinese character in two known
|
||||
-- signatures. Restore only the rows whose primary key and corrupt byte sequence
|
||||
-- both match the pre-migration backup, so later user edits cannot be overwritten.
|
||||
UPDATE "SmsSignature"
|
||||
SET name = convert_from(
|
||||
decode('e38090e888aae5a4a9e4bfa1e681afe4bfa1e8afbae7bd91e38091', 'hex'),
|
||||
'SQL_ASCII'
|
||||
)
|
||||
WHERE id IN (
|
||||
'cmrj2ci1r00544pnkt64wza4e',
|
||||
'cmrobgqp7002nwenke5wh09fr'
|
||||
)
|
||||
AND encode(convert_to(name, 'SQL_ASCII'), 'hex') =
|
||||
'e38090e888aae5a4a9e4bfa1e681afe4bfa1e8afbae7bde38091';
|
||||
|
||||
-- Rollback guidance: do not restore the invalid byte sequence. If rollback is
|
||||
-- required, keep the repaired UTF-8 values and only roll back application code.
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
CREATE TABLE "CmppInboundLongMessage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"groupKey" TEXT NOT NULL,
|
||||
"account" TEXT NOT NULL,
|
||||
"srcId" TEXT,
|
||||
"phoneNumbers" JSONB NOT NULL,
|
||||
"concatReference" INTEGER NOT NULL,
|
||||
"segmentTotal" INTEGER NOT NULL,
|
||||
"msgFmt" INTEGER NOT NULL,
|
||||
"messageId" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'collecting',
|
||||
"response" JSONB,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"completedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "CmppInboundLongMessage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "CmppInboundLongMessageSegment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"groupId" TEXT NOT NULL,
|
||||
"segmentIndex" INTEGER NOT NULL,
|
||||
"sequenceId" TEXT,
|
||||
"content" TEXT NOT NULL,
|
||||
"contentHash" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "CmppInboundLongMessageSegment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "CmppInboundLongMessage_messageId_key"
|
||||
ON "CmppInboundLongMessage"("messageId");
|
||||
|
||||
CREATE INDEX "CmppInboundLongMessage_groupKey_status_createdAt_idx"
|
||||
ON "CmppInboundLongMessage"("groupKey", "status", "createdAt");
|
||||
|
||||
CREATE INDEX "CmppInboundLongMessage_applicationId_status_expiresAt_idx"
|
||||
ON "CmppInboundLongMessage"("applicationId", "status", "expiresAt");
|
||||
|
||||
CREATE UNIQUE INDEX "CmppInboundLongMessageSegment_groupId_segmentIndex_key"
|
||||
ON "CmppInboundLongMessageSegment"("groupId", "segmentIndex");
|
||||
|
||||
CREATE INDEX "CmppInboundLongMessageSegment_sequenceId_idx"
|
||||
ON "CmppInboundLongMessageSegment"("sequenceId");
|
||||
|
||||
ALTER TABLE "CmppInboundLongMessage"
|
||||
ADD CONSTRAINT "CmppInboundLongMessage_tenantId_fkey"
|
||||
FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "CmppInboundLongMessage"
|
||||
ADD CONSTRAINT "CmppInboundLongMessage_applicationId_fkey"
|
||||
FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "CmppInboundLongMessageSegment"
|
||||
ADD CONSTRAINT "CmppInboundLongMessageSegment_groupId_fkey"
|
||||
FOREIGN KEY ("groupId") REFERENCES "CmppInboundLongMessage"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,14 @@
|
||||
ALTER TABLE "CmppConnectionState"
|
||||
ADD COLUMN "lastReconnectAttemptAt" TIMESTAMP(3),
|
||||
ADD COLUMN "nextReconnectAt" TIMESTAMP(3),
|
||||
ADD COLUMN "lastErrorCategory" TEXT;
|
||||
|
||||
CREATE INDEX "CmppConnectionState_status_nextReconnectAt_idx"
|
||||
ON "CmppConnectionState"("status", "nextReconnectAt");
|
||||
|
||||
-- Rollback:
|
||||
-- DROP INDEX "CmppConnectionState_status_nextReconnectAt_idx";
|
||||
-- ALTER TABLE "CmppConnectionState"
|
||||
-- DROP COLUMN "lastErrorCategory",
|
||||
-- DROP COLUMN "nextReconnectAt",
|
||||
-- DROP COLUMN "lastReconnectAttemptAt";
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "SmsApplication"
|
||||
ALTER COLUMN "maxPhonesPerTask" SET DEFAULT 10000;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
ALTER TABLE "DailyReconciliationReport"
|
||||
ADD COLUMN "submittedUnits" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "unknownUnits" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE "DailyProfitReport"
|
||||
ADD COLUMN "submittedUnits" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "unknownUnits" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE "DailyQualityReport"
|
||||
ADD COLUMN "submittedUnits" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "unknownUnits" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
UPDATE "DailyReconciliationReport"
|
||||
SET "submittedUnits" = "sentUnits",
|
||||
"unknownUnits" = GREATEST("sentUnits" - "successUnits" - "failedUnits", 0);
|
||||
|
||||
UPDATE "DailyProfitReport"
|
||||
SET "submittedUnits" = "sentUnits",
|
||||
"unknownUnits" = GREATEST("sentUnits" - "successUnits" - "failedUnits", 0);
|
||||
|
||||
UPDATE "DailyQualityReport"
|
||||
SET "submittedUnits" = "sentUnits",
|
||||
"unknownUnits" = GREATEST("sentUnits" - "successUnits" - "failedUnits", 0);
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
WITH ranked AS (
|
||||
SELECT
|
||||
id,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY "channelId", "connectionId"
|
||||
ORDER BY "updatedAt" DESC, "createdAt" DESC, id DESC
|
||||
) AS row_no
|
||||
FROM "CmppConnectionState"
|
||||
WHERE "applicationId" IS NULL
|
||||
)
|
||||
DELETE FROM "CmppConnectionState" state
|
||||
USING ranked
|
||||
WHERE state.id = ranked.id
|
||||
AND ranked.row_no > 1;
|
||||
|
||||
CREATE UNIQUE INDEX "CmppConnectionState_supplier_channel_connection_key"
|
||||
ON "CmppConnectionState"("channelId", "connectionId")
|
||||
WHERE "applicationId" IS NULL;
|
||||
|
||||
-- Rollback:
|
||||
-- DROP INDEX "CmppConnectionState_supplier_channel_connection_key";
|
||||
@@ -0,0 +1,34 @@
|
||||
CREATE TABLE "ProtocolInteractionLog" (
|
||||
"id" TEXT NOT NULL,
|
||||
"protocol" TEXT NOT NULL,
|
||||
"direction" TEXT NOT NULL,
|
||||
"eventType" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL,
|
||||
"tenantId" TEXT,
|
||||
"applicationId" TEXT,
|
||||
"channelId" TEXT,
|
||||
"account" TEXT,
|
||||
"messageId" TEXT,
|
||||
"gatewayMessageId" TEXT,
|
||||
"traceId" TEXT,
|
||||
"requestId" TEXT,
|
||||
"phoneMasked" TEXT,
|
||||
"resultCode" TEXT,
|
||||
"durationMs" INTEGER,
|
||||
"payloadBytes" INTEGER,
|
||||
"retryCount" INTEGER,
|
||||
"detail" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "ProtocolInteractionLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "ProtocolInteractionLog_createdAt_idx" ON "ProtocolInteractionLog"("createdAt");
|
||||
CREATE INDEX "ProtocolInteractionLog_protocol_direction_createdAt_idx" ON "ProtocolInteractionLog"("protocol", "direction", "createdAt");
|
||||
CREATE INDEX "ProtocolInteractionLog_eventType_createdAt_idx" ON "ProtocolInteractionLog"("eventType", "createdAt");
|
||||
CREATE INDEX "ProtocolInteractionLog_status_createdAt_idx" ON "ProtocolInteractionLog"("status", "createdAt");
|
||||
CREATE INDEX "ProtocolInteractionLog_channelId_createdAt_idx" ON "ProtocolInteractionLog"("channelId", "createdAt");
|
||||
CREATE INDEX "ProtocolInteractionLog_applicationId_createdAt_idx" ON "ProtocolInteractionLog"("applicationId", "createdAt");
|
||||
CREATE INDEX "ProtocolInteractionLog_messageId_idx" ON "ProtocolInteractionLog"("messageId");
|
||||
CREATE INDEX "ProtocolInteractionLog_gatewayMessageId_idx" ON "ProtocolInteractionLog"("gatewayMessageId");
|
||||
CREATE INDEX "ProtocolInteractionLog_requestId_idx" ON "ProtocolInteractionLog"("requestId");
|
||||
@@ -0,0 +1,17 @@
|
||||
UPDATE "SmsApplicationHttpConfig" config
|
||||
SET
|
||||
"receiptDeliveryMode" = CASE
|
||||
WHEN application."interfaceEnabled" AND config.enabled THEN 'both'
|
||||
WHEN application."interfaceEnabled" THEN 'cmpp'
|
||||
WHEN config.enabled THEN 'http'
|
||||
ELSE 'none'
|
||||
END,
|
||||
"uplinkDeliveryMode" = CASE
|
||||
WHEN application."interfaceEnabled" AND config.enabled THEN 'both'
|
||||
WHEN application."interfaceEnabled" THEN 'cmpp'
|
||||
WHEN config.enabled THEN 'http'
|
||||
ELSE 'none'
|
||||
END,
|
||||
"updatedAt" = CURRENT_TIMESTAMP
|
||||
FROM "SmsApplication" application
|
||||
WHERE application.id = config."applicationId";
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
CREATE TABLE "CmppDownstreamDeliveryAttempt" (
|
||||
"id" TEXT NOT NULL,
|
||||
"deliveryId" TEXT NOT NULL,
|
||||
"attemptKey" TEXT NOT NULL,
|
||||
"attemptNo" INTEGER NOT NULL,
|
||||
"connectionId" TEXT,
|
||||
"sequenceId" TEXT,
|
||||
"messageId" TEXT,
|
||||
"status" TEXT NOT NULL,
|
||||
"sentAt" TIMESTAMP(3),
|
||||
"ackDeadlineAt" TIMESTAMP(3),
|
||||
"acknowledgedAt" TIMESTAMP(3),
|
||||
"ackResult" INTEGER,
|
||||
"failureType" TEXT,
|
||||
"errorMessage" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "CmppDownstreamDeliveryAttempt_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "UpstreamReceiptInbox" (
|
||||
"id" TEXT NOT NULL,
|
||||
"receiptKey" TEXT NOT NULL,
|
||||
"incomingChannelId" TEXT NOT NULL,
|
||||
"incomingConnectionId" TEXT,
|
||||
"upstreamAccount" TEXT NOT NULL,
|
||||
"upstreamHost" TEXT NOT NULL,
|
||||
"upstreamPort" INTEGER NOT NULL,
|
||||
"protocol" TEXT NOT NULL,
|
||||
"protocolVersion" TEXT NOT NULL,
|
||||
"provisionalMessageId" TEXT,
|
||||
"sequenceId" INTEGER,
|
||||
"gatewayMessageId" TEXT NOT NULL,
|
||||
"phoneNumber" TEXT,
|
||||
"receiptStatus" TEXT NOT NULL,
|
||||
"rawStatus" TEXT NOT NULL,
|
||||
"errorCode" TEXT,
|
||||
"errorMessage" TEXT,
|
||||
"deliveredAt" TIMESTAMP(3) NOT NULL,
|
||||
"receivedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"status" TEXT NOT NULL DEFAULT 'pending',
|
||||
"matchedMessageRecordId" TEXT,
|
||||
"matchedSubmitRecordId" TEXT,
|
||||
"matchedChannelId" TEXT,
|
||||
"attemptCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"nextRetryAt" TIMESTAMP(3),
|
||||
"lastError" TEXT,
|
||||
"processedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "UpstreamReceiptInbox_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "CmppDownstreamDeliveryAttempt_attemptKey_key"
|
||||
ON "CmppDownstreamDeliveryAttempt"("attemptKey");
|
||||
|
||||
CREATE INDEX "CmppDownstreamDeliveryAttempt_deliveryId_attemptNo_idx"
|
||||
ON "CmppDownstreamDeliveryAttempt"("deliveryId", "attemptNo");
|
||||
|
||||
CREATE INDEX "CmppDownstreamDeliveryAttempt_status_ackDeadlineAt_idx"
|
||||
ON "CmppDownstreamDeliveryAttempt"("status", "ackDeadlineAt");
|
||||
|
||||
CREATE INDEX "CmppDownstreamDeliveryAttempt_connectionId_sequenceId_idx"
|
||||
ON "CmppDownstreamDeliveryAttempt"("connectionId", "sequenceId");
|
||||
|
||||
CREATE UNIQUE INDEX "UpstreamReceiptInbox_receiptKey_key"
|
||||
ON "UpstreamReceiptInbox"("receiptKey");
|
||||
|
||||
CREATE INDEX "UpstreamReceiptInbox_status_nextRetryAt_receivedAt_idx"
|
||||
ON "UpstreamReceiptInbox"("status", "nextRetryAt", "receivedAt");
|
||||
|
||||
CREATE INDEX "UpstreamReceiptInbox_gatewayMessageId_phoneNumber_idx"
|
||||
ON "UpstreamReceiptInbox"("gatewayMessageId", "phoneNumber");
|
||||
|
||||
CREATE INDEX "UpstreamReceiptInbox_incomingChannelId_receivedAt_idx"
|
||||
ON "UpstreamReceiptInbox"("incomingChannelId", "receivedAt");
|
||||
|
||||
CREATE INDEX "UpstreamReceiptInbox_matchedMessageRecordId_idx"
|
||||
ON "UpstreamReceiptInbox"("matchedMessageRecordId");
|
||||
|
||||
ALTER TABLE "CmppDownstreamDeliveryAttempt"
|
||||
ADD CONSTRAINT "CmppDownstreamDeliveryAttempt_deliveryId_fkey"
|
||||
FOREIGN KEY ("deliveryId") REFERENCES "CmppDownstreamDelivery"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,15 @@
|
||||
DROP INDEX IF EXISTS "User_username_key";
|
||||
DROP INDEX IF EXISTS "User_email_key";
|
||||
DROP INDEX IF EXISTS "User_phone_key";
|
||||
|
||||
CREATE UNIQUE INDEX "User_active_username_key"
|
||||
ON "User" ("username")
|
||||
WHERE "deletedAt" IS NULL;
|
||||
|
||||
CREATE UNIQUE INDEX "User_active_email_key"
|
||||
ON "User" ("email")
|
||||
WHERE "deletedAt" IS NULL AND "email" IS NOT NULL;
|
||||
|
||||
CREATE UNIQUE INDEX "User_active_phone_key"
|
||||
ON "User" ("phone")
|
||||
WHERE "deletedAt" IS NULL AND "phone" IS NOT NULL;
|
||||
@@ -0,0 +1,7 @@
|
||||
ALTER TABLE "SmsApplication"
|
||||
ADD COLUMN "disablingAt" TIMESTAMP(3),
|
||||
ADD COLUMN "autoDisableAt" TIMESTAMP(3),
|
||||
ADD COLUMN "disableReason" TEXT;
|
||||
|
||||
CREATE INDEX "SmsApplication_status_autoDisableAt_idx"
|
||||
ON "SmsApplication"("status", "autoDisableAt");
|
||||
@@ -0,0 +1,39 @@
|
||||
-- Risk thresholds now inherit from global rules and may be overridden per application.
|
||||
-- Existing maxPhonesPerTask values are intentionally discarded because this environment
|
||||
-- contains test-only application data and the product owner explicitly declined migration.
|
||||
ALTER TABLE "RiskRule" ADD COLUMN "applicationId" TEXT;
|
||||
|
||||
-- Retain historical rules and hit records for audit, but remove them from the
|
||||
-- effective rule set and configuration page.
|
||||
UPDATE "RiskRule"
|
||||
SET "status" = 'deleted'
|
||||
WHERE "code" IN (
|
||||
'DUPLICATE_PHONE_RATIO',
|
||||
'ILLEGAL_PHONE_RATIO',
|
||||
'BLACKLIST_HIT_RATIO',
|
||||
'TEMPLATE_VARIABLE_ANOMALY'
|
||||
);
|
||||
|
||||
UPDATE "RiskRule"
|
||||
SET "status" = 'deleted'
|
||||
WHERE "tenantId" IS NOT NULL AND "applicationId" IS NULL;
|
||||
|
||||
DROP INDEX IF EXISTS "RiskRule_tenantId_code_key";
|
||||
DROP INDEX IF EXISTS "RiskRule_tenantId_status_priority_idx";
|
||||
|
||||
CREATE UNIQUE INDEX "RiskRule_applicationId_code_key"
|
||||
ON "RiskRule"("applicationId", "code");
|
||||
|
||||
CREATE UNIQUE INDEX "RiskRule_global_code_key"
|
||||
ON "RiskRule"("code")
|
||||
WHERE "applicationId" IS NULL AND "status" <> 'deleted';
|
||||
|
||||
CREATE INDEX "RiskRule_tenantId_applicationId_status_priority_idx"
|
||||
ON "RiskRule"("tenantId", "applicationId", "status", "priority");
|
||||
|
||||
ALTER TABLE "RiskRule"
|
||||
ADD CONSTRAINT "RiskRule_applicationId_fkey"
|
||||
FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "SmsApplication" DROP COLUMN "maxPhonesPerTask";
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
ALTER TABLE "SmsSubmitRecord"
|
||||
ADD COLUMN "retryOfSubmitRecordId" TEXT;
|
||||
|
||||
CREATE UNIQUE INDEX "SmsSubmitRecord_retryOfSubmitRecordId_key"
|
||||
ON "SmsSubmitRecord"("retryOfSubmitRecordId");
|
||||
|
||||
ALTER TABLE "SmsSubmitRecord"
|
||||
ADD CONSTRAINT "SmsSubmitRecord_retryOfSubmitRecordId_fkey"
|
||||
FOREIGN KEY ("retryOfSubmitRecordId") REFERENCES "SmsSubmitRecord"("id")
|
||||
ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "AccountTransaction"
|
||||
ADD COLUMN "idempotencyKey" TEXT;
|
||||
|
||||
CREATE UNIQUE INDEX "AccountTransaction_idempotencyKey_key"
|
||||
ON "AccountTransaction"("idempotencyKey");
|
||||
|
||||
ALTER TABLE "CmppDownstreamDelivery"
|
||||
ADD COLUMN "dedupeKey" TEXT;
|
||||
|
||||
WITH ranked_receipts AS (
|
||||
SELECT id,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY "messageRecordId", "deliveryType"
|
||||
ORDER BY "createdAt", id
|
||||
) AS row_number
|
||||
FROM "CmppDownstreamDelivery"
|
||||
WHERE "messageRecordId" IS NOT NULL
|
||||
AND "deliveryType" = 'receipt'
|
||||
)
|
||||
UPDATE "CmppDownstreamDelivery" AS delivery
|
||||
SET "dedupeKey" = 'receipt:' || delivery."messageRecordId"
|
||||
FROM ranked_receipts
|
||||
WHERE delivery.id = ranked_receipts.id
|
||||
AND ranked_receipts.row_number = 1;
|
||||
|
||||
CREATE UNIQUE INDEX "CmppDownstreamDelivery_dedupeKey_key"
|
||||
ON "CmppDownstreamDelivery"("dedupeKey");
|
||||
@@ -0,0 +1,37 @@
|
||||
ALTER TABLE "SmsSubmitRecord"
|
||||
ADD COLUMN "channelGroupId" TEXT,
|
||||
ADD COLUMN "channelGroupName" TEXT;
|
||||
|
||||
ALTER TABLE "SmsSubmitRecord"
|
||||
ADD CONSTRAINT "SmsSubmitRecord_channelGroupId_fkey"
|
||||
FOREIGN KEY ("channelGroupId") REFERENCES "SmsChannelGroup"("id")
|
||||
ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
CREATE INDEX "SmsSubmitRecord_channelGroupId_idx"
|
||||
ON "SmsSubmitRecord"("channelGroupId");
|
||||
|
||||
UPDATE "SmsSubmitRecord" submit
|
||||
SET
|
||||
"channelGroupId" = matched."groupId",
|
||||
"channelGroupName" = matched."groupName"
|
||||
FROM (
|
||||
SELECT DISTINCT ON (submit_record.id)
|
||||
submit_record.id,
|
||||
route."groupId",
|
||||
channel_group.name AS "groupName"
|
||||
FROM "SmsSubmitRecord" submit_record
|
||||
JOIN "SmsMessageRecord" message ON message.id = submit_record."messageRecordId"
|
||||
JOIN "ChannelRouteRule" route
|
||||
ON route."tenantId" = message."tenantId"
|
||||
AND route."applicationId" = message."applicationId"
|
||||
AND route.carrier = message.carrier
|
||||
AND route."channelId" IS NULL
|
||||
AND route.province IS NULL
|
||||
JOIN "SmsChannelGroupItem" group_item
|
||||
ON group_item."groupId" = route."groupId"
|
||||
AND group_item."channelId" = submit_record."channelId"
|
||||
JOIN "SmsChannelGroup" channel_group ON channel_group.id = route."groupId"
|
||||
WHERE route.status = 'active'
|
||||
ORDER BY submit_record.id, route.priority ASC, route."createdAt" ASC
|
||||
) matched
|
||||
WHERE submit.id = matched.id;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
ALTER TABLE "ReportMaterialImportBatch"
|
||||
ADD COLUMN "reviewedById" TEXT,
|
||||
ADD COLUMN "reviewedAt" TIMESTAMP(3);
|
||||
|
||||
CREATE TABLE "ReportMaterialImportItem" (
|
||||
"id" TEXT NOT NULL,
|
||||
"batchId" TEXT NOT NULL,
|
||||
"rowNumber" INTEGER NOT NULL,
|
||||
"reportType" TEXT NOT NULL,
|
||||
"operation" TEXT NOT NULL,
|
||||
"targetId" TEXT,
|
||||
"status" TEXT NOT NULL DEFAULT 'pending_review',
|
||||
"payload" JSONB NOT NULL,
|
||||
"originalSnapshot" JSONB,
|
||||
"errorMessage" TEXT,
|
||||
"reviewReason" TEXT,
|
||||
"reviewedById" TEXT,
|
||||
"reviewedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ReportMaterialImportItem_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "ReportMaterialImportItem_batchId_fkey"
|
||||
FOREIGN KEY ("batchId") REFERENCES "ReportMaterialImportBatch"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "ReportMaterialImportItem_batchId_rowNumber_key"
|
||||
ON "ReportMaterialImportItem"("batchId", "rowNumber");
|
||||
CREATE INDEX "ReportMaterialImportItem_batchId_status_idx"
|
||||
ON "ReportMaterialImportItem"("batchId", "status");
|
||||
CREATE INDEX "ReportMaterialImportItem_reportType_status_createdAt_idx"
|
||||
ON "ReportMaterialImportItem"("reportType", "status", "createdAt");
|
||||
CREATE INDEX "ReportMaterialImportItem_targetId_idx"
|
||||
ON "ReportMaterialImportItem"("targetId");
|
||||
@@ -0,0 +1,9 @@
|
||||
CREATE INDEX "RechargeOrder_payMethod_createdAt_idx" ON "RechargeOrder"("payMethod", "createdAt");
|
||||
CREATE INDEX "SmsApplication_status_createdAt_idx" ON "SmsApplication"("status", "createdAt");
|
||||
CREATE INDEX "SmsSignature_tenantId_updatedAt_idx" ON "SmsSignature"("tenantId", "updatedAt");
|
||||
CREATE INDEX "SmsTemplate_tenantId_auditStatus_createdAt_idx" ON "SmsTemplate"("tenantId", "auditStatus", "createdAt");
|
||||
CREATE INDEX "SmsChannel_status_createdAt_idx" ON "SmsChannel"("status", "createdAt");
|
||||
CREATE INDEX "ChannelSignatureReportTask_status_createdAt_idx" ON "ChannelSignatureReportTask"("status", "createdAt");
|
||||
CREATE INDEX "ChannelSignatureReportRecord_createdAt_idx" ON "ChannelSignatureReportRecord"("createdAt");
|
||||
CREATE INDEX "SmsMessageRecord_queuedAt_idx" ON "SmsMessageRecord"("queuedAt");
|
||||
CREATE INDEX "SmsUplinkMessage_receivedAt_idx" ON "SmsUplinkMessage"("receivedAt");
|
||||
@@ -0,0 +1,131 @@
|
||||
CREATE TABLE "PhoneFrequencyHit" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"ruleId" TEXT,
|
||||
"ruleCode" TEXT NOT NULL,
|
||||
"ruleName" TEXT NOT NULL,
|
||||
"phoneNumber" TEXT NOT NULL,
|
||||
"thresholdValue" INTEGER NOT NULL,
|
||||
"actualValue" INTEGER NOT NULL,
|
||||
"windowStartedAt" TIMESTAMP(3) NOT NULL,
|
||||
"windowEndsAt" TIMESTAMP(3) NOT NULL,
|
||||
"generation" INTEGER NOT NULL DEFAULT 0,
|
||||
"action" TEXT NOT NULL DEFAULT 'block',
|
||||
"sourceType" TEXT,
|
||||
"releasedAt" TIMESTAMP(3),
|
||||
"releasedById" TEXT,
|
||||
"releaseReason" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "PhoneFrequencyHit_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "PhoneFrequencyState" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"ruleId" TEXT,
|
||||
"ruleCode" TEXT NOT NULL,
|
||||
"phoneNumber" TEXT NOT NULL,
|
||||
"windowStartedAt" TIMESTAMP(3) NOT NULL,
|
||||
"windowEndsAt" TIMESTAMP(3) NOT NULL,
|
||||
"count" INTEGER NOT NULL DEFAULT 0,
|
||||
"generation" INTEGER NOT NULL DEFAULT 0,
|
||||
"activeHitId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "PhoneFrequencyState_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "PhoneFrequencyHit_applicationId_ruleCode_phoneNumber_windowStartedAt_generation_key"
|
||||
ON "PhoneFrequencyHit"("applicationId", "ruleCode", "phoneNumber", "windowStartedAt", "generation");
|
||||
|
||||
CREATE INDEX "PhoneFrequencyHit_tenantId_applicationId_createdAt_idx"
|
||||
ON "PhoneFrequencyHit"("tenantId", "applicationId", "createdAt");
|
||||
|
||||
CREATE INDEX "PhoneFrequencyHit_applicationId_phoneNumber_createdAt_idx"
|
||||
ON "PhoneFrequencyHit"("applicationId", "phoneNumber", "createdAt");
|
||||
|
||||
CREATE INDEX "PhoneFrequencyHit_windowEndsAt_releasedAt_idx"
|
||||
ON "PhoneFrequencyHit"("windowEndsAt", "releasedAt");
|
||||
|
||||
CREATE INDEX "PhoneFrequencyHit_ruleId_idx" ON "PhoneFrequencyHit"("ruleId");
|
||||
CREATE INDEX "PhoneFrequencyHit_releasedById_idx" ON "PhoneFrequencyHit"("releasedById");
|
||||
|
||||
CREATE UNIQUE INDEX "PhoneFrequencyState_activeHitId_key" ON "PhoneFrequencyState"("activeHitId");
|
||||
|
||||
CREATE UNIQUE INDEX "PhoneFrequencyState_applicationId_ruleCode_phoneNumber_key"
|
||||
ON "PhoneFrequencyState"("applicationId", "ruleCode", "phoneNumber");
|
||||
|
||||
CREATE INDEX "PhoneFrequencyState_tenantId_applicationId_windowEndsAt_idx"
|
||||
ON "PhoneFrequencyState"("tenantId", "applicationId", "windowEndsAt");
|
||||
|
||||
CREATE INDEX "PhoneFrequencyState_ruleId_idx" ON "PhoneFrequencyState"("ruleId");
|
||||
|
||||
ALTER TABLE "PhoneFrequencyHit"
|
||||
ADD CONSTRAINT "PhoneFrequencyHit_tenantId_fkey"
|
||||
FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "PhoneFrequencyHit"
|
||||
ADD CONSTRAINT "PhoneFrequencyHit_applicationId_fkey"
|
||||
FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "PhoneFrequencyHit"
|
||||
ADD CONSTRAINT "PhoneFrequencyHit_ruleId_fkey"
|
||||
FOREIGN KEY ("ruleId") REFERENCES "RiskRule"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "PhoneFrequencyHit"
|
||||
ADD CONSTRAINT "PhoneFrequencyHit_releasedById_fkey"
|
||||
FOREIGN KEY ("releasedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "PhoneFrequencyState"
|
||||
ADD CONSTRAINT "PhoneFrequencyState_tenantId_fkey"
|
||||
FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "PhoneFrequencyState"
|
||||
ADD CONSTRAINT "PhoneFrequencyState_applicationId_fkey"
|
||||
FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "PhoneFrequencyState"
|
||||
ADD CONSTRAINT "PhoneFrequencyState_ruleId_fkey"
|
||||
FOREIGN KEY ("ruleId") REFERENCES "RiskRule"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "PhoneFrequencyState"
|
||||
ADD CONSTRAINT "PhoneFrequencyState_activeHitId_fkey"
|
||||
FOREIGN KEY ("activeHitId") REFERENCES "PhoneFrequencyHit"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
INSERT INTO "RiskRule" (
|
||||
"id", "tenantId", "applicationId", "code", "name", "description", "metric",
|
||||
"thresholdValue", "action", "status", "priority", "config", "createdAt", "updatedAt"
|
||||
)
|
||||
SELECT
|
||||
'default-phone-frequency-24h', NULL, NULL, 'PHONE_FREQUENCY_24H',
|
||||
'单号码24小时发送频次', '同一企业应用下,单个号码在北京时间自然日内最多允许10条业务短信。',
|
||||
'phoneFrequencyCount', 10, 'block', 'active', 40,
|
||||
'{"periodSeconds":86400,"timeZone":"Asia/Shanghai","alignment":"fixed"}'::jsonb,
|
||||
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM "RiskRule"
|
||||
WHERE "applicationId" IS NULL
|
||||
AND "code" = 'PHONE_FREQUENCY_24H'
|
||||
AND "status" <> 'deleted'
|
||||
);
|
||||
|
||||
INSERT INTO "RiskRule" (
|
||||
"id", "tenantId", "applicationId", "code", "name", "description", "metric",
|
||||
"thresholdValue", "action", "status", "priority", "config", "createdAt", "updatedAt"
|
||||
)
|
||||
SELECT
|
||||
'default-phone-frequency-5m', NULL, NULL, 'PHONE_FREQUENCY_5M',
|
||||
'单号码5分钟发送频次', '同一企业应用下,单个号码在固定5分钟周期内最多允许5条业务短信。',
|
||||
'phoneFrequencyCount', 5, 'block', 'active', 50,
|
||||
'{"periodSeconds":300,"timeZone":"Asia/Shanghai","alignment":"fixed"}'::jsonb,
|
||||
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM "RiskRule"
|
||||
WHERE "applicationId" IS NULL
|
||||
AND "code" = 'PHONE_FREQUENCY_5M'
|
||||
AND "status" <> 'deleted'
|
||||
);
|
||||
@@ -0,0 +1,34 @@
|
||||
CREATE TABLE "PhoneFrequencyWhitelist" (
|
||||
"id" TEXT NOT NULL,
|
||||
"phoneNumber" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'active',
|
||||
"reason" TEXT NOT NULL,
|
||||
"remark" TEXT,
|
||||
"createdById" TEXT NOT NULL,
|
||||
"updatedById" TEXT NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "PhoneFrequencyWhitelist_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "PhoneFrequencyWhitelist_phoneNumber_key"
|
||||
ON "PhoneFrequencyWhitelist"("phoneNumber");
|
||||
|
||||
CREATE INDEX "PhoneFrequencyWhitelist_status_updatedAt_idx"
|
||||
ON "PhoneFrequencyWhitelist"("status", "updatedAt");
|
||||
|
||||
CREATE INDEX "PhoneFrequencyWhitelist_createdById_idx"
|
||||
ON "PhoneFrequencyWhitelist"("createdById");
|
||||
|
||||
CREATE INDEX "PhoneFrequencyWhitelist_updatedById_idx"
|
||||
ON "PhoneFrequencyWhitelist"("updatedById");
|
||||
|
||||
ALTER TABLE "PhoneFrequencyWhitelist"
|
||||
ADD CONSTRAINT "PhoneFrequencyWhitelist_createdById_fkey"
|
||||
FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "PhoneFrequencyWhitelist"
|
||||
ADD CONSTRAINT "PhoneFrequencyWhitelist_updatedById_fkey"
|
||||
FOREIGN KEY ("updatedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,34 @@
|
||||
CREATE TABLE "DrainageDetectionRule" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"category" TEXT NOT NULL,
|
||||
"pattern" TEXT NOT NULL,
|
||||
"flags" TEXT NOT NULL DEFAULT 'giu',
|
||||
"priority" INTEGER NOT NULL DEFAULT 100,
|
||||
"status" TEXT NOT NULL DEFAULT 'active',
|
||||
"description" TEXT,
|
||||
"version" INTEGER NOT NULL DEFAULT 1,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "DrainageDetectionRule_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "DrainageDetectionRule_code_key" ON "DrainageDetectionRule"("code");
|
||||
CREATE INDEX "DrainageDetectionRule_status_priority_idx" ON "DrainageDetectionRule"("status", "priority");
|
||||
|
||||
ALTER TABLE "SmsMessageRecord"
|
||||
ADD COLUMN "hasDrainageContent" BOOLEAN,
|
||||
ADD COLUMN "drainageDetection" JSONB,
|
||||
ADD COLUMN "drainageDetectionVersion" TEXT,
|
||||
ADD COLUMN "drainageEvaluatedAt" TIMESTAMP(3);
|
||||
|
||||
CREATE INDEX "SmsMessageRecord_hasDrainageContent_queuedAt_idx"
|
||||
ON "SmsMessageRecord"("hasDrainageContent", "queuedAt");
|
||||
|
||||
INSERT INTO "DrainageDetectionRule"
|
||||
("id", "code", "name", "category", "pattern", "flags", "priority", "status", "description", "version", "updatedAt")
|
||||
VALUES
|
||||
('drainage-rule-url', 'URL', 'URL及裸域名', 'url', $regex$(?:https?:\/\/)?(?:www\.)?(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,24}|(?:\d{1,3}\.){3}\d{1,3})(?::\d{1,5})?(?:\/[^\s,,;;!!??<>《》]*)?$regex$, 'giu', 10, 'active', '识别协议链接、裸域名、短链接及IP地址链接;邮箱区间由检测器排除', 1, CURRENT_TIMESTAMP),
|
||||
('drainage-rule-mobile', 'MOBILE', '手机号码', 'mobile', $regex$(?:^|[^0-9])((?:\+?86)?1[3-9][0-9]{9})(?:$|[^0-9])$regex$, 'giu', 20, 'active', '规范化后识别+86、空格、短横线及中文标点拆分手机号', 1, CURRENT_TIMESTAMP),
|
||||
('drainage-rule-landline', 'LANDLINE', '固定电话号码', 'landline', $regex$(?:^|[^0-9])((?:\+?86)?(?:\(0[0-9]{2,3}\)|0[0-9]{2,3})-?[0-9]{7,8}(?:(?:转|分机|ext)[0-9]{1,6})?)(?:$|[^0-9])$regex$, 'giu', 30, 'active', '识别区号括号、分隔符和分机号', 1, CURRENT_TIMESTAMP);
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
-- PostgreSQL standard-conforming strings preserve backslashes literally. The initial
|
||||
-- seed used JavaScript-style escaping, so already-migrated databases need their three
|
||||
-- built-in patterns normalized to the single backslashes expected by RegExp.
|
||||
UPDATE "DrainageDetectionRule"
|
||||
SET
|
||||
"pattern" = $regex$(?:https?:\/\/)?(?:www\.)?(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,24}|(?:\d{1,3}\.){3}\d{1,3})(?::\d{1,5})?(?:\/[^\s,,;;!!??<>《》]*)?$regex$,
|
||||
"version" = "version" + 1,
|
||||
"updatedAt" = CURRENT_TIMESTAMP
|
||||
WHERE "code" = 'URL';
|
||||
|
||||
UPDATE "DrainageDetectionRule"
|
||||
SET
|
||||
"pattern" = $regex$(?:^|[^0-9])((?:\+?86)?1[3-9][0-9]{9})(?:$|[^0-9])$regex$,
|
||||
"version" = "version" + 1,
|
||||
"updatedAt" = CURRENT_TIMESTAMP
|
||||
WHERE "code" = 'MOBILE';
|
||||
|
||||
UPDATE "DrainageDetectionRule"
|
||||
SET
|
||||
"pattern" = $regex$(?:^|[^0-9])((?:\+?86)?(?:\(0[0-9]{2,3}\)|0[0-9]{2,3})-?[0-9]{7,8}(?:(?:转|分机|ext)[0-9]{1,6})?)(?:$|[^0-9])$regex$,
|
||||
"version" = "version" + 1,
|
||||
"updatedAt" = CURRENT_TIMESTAMP
|
||||
WHERE "code" = 'LANDLINE';
|
||||
@@ -0,0 +1,12 @@
|
||||
ALTER TABLE "SmsMessageRecord"
|
||||
ADD COLUMN "cmppRegisteredDelivery" BOOLEAN,
|
||||
ADD COLUMN "timeoutReceiptQueuedAt" TIMESTAMP(3);
|
||||
|
||||
ALTER TABLE "CmppInboundLongMessageSegment"
|
||||
ADD COLUMN "registeredDelivery" BOOLEAN NOT NULL DEFAULT true;
|
||||
|
||||
-- Historical CMPP submissions were accepted before Registered_Delivery was
|
||||
-- persisted. Preserve their existing receipt-enabled behavior.
|
||||
UPDATE "SmsMessageRecord"
|
||||
SET "cmppRegisteredDelivery" = true
|
||||
WHERE "cmppSubmitSequenceId" IS NOT NULL;
|
||||
@@ -0,0 +1,43 @@
|
||||
CREATE TABLE "SmsReceiptAnomaly" (
|
||||
"id" TEXT NOT NULL,
|
||||
"anomalyKey" TEXT NOT NULL,
|
||||
"tenantId" TEXT,
|
||||
"applicationId" TEXT,
|
||||
"channelId" TEXT,
|
||||
"messageRecordId" TEXT,
|
||||
"submitRecordId" TEXT,
|
||||
"receiptRecordId" TEXT,
|
||||
"anomalyType" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'pending',
|
||||
"previousStatus" TEXT,
|
||||
"incomingStatus" TEXT,
|
||||
"rawStatus" TEXT,
|
||||
"errorCode" TEXT,
|
||||
"detail" JSONB,
|
||||
"occurrenceCount" INTEGER NOT NULL DEFAULT 1,
|
||||
"firstOccurredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"lastOccurredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"resolvedAt" TIMESTAMP(3),
|
||||
"resolutionNote" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SmsReceiptAnomaly_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "SmsReceiptAnomaly_anomalyKey_key" ON "SmsReceiptAnomaly"("anomalyKey");
|
||||
CREATE INDEX "SmsReceiptAnomaly_status_lastOccurredAt_idx" ON "SmsReceiptAnomaly"("status", "lastOccurredAt");
|
||||
CREATE INDEX "SmsReceiptAnomaly_lastOccurredAt_idx" ON "SmsReceiptAnomaly"("lastOccurredAt");
|
||||
CREATE INDEX "SmsReceiptAnomaly_anomalyType_lastOccurredAt_idx" ON "SmsReceiptAnomaly"("anomalyType", "lastOccurredAt");
|
||||
CREATE INDEX "SmsReceiptAnomaly_tenantId_status_lastOccurredAt_idx" ON "SmsReceiptAnomaly"("tenantId", "status", "lastOccurredAt");
|
||||
CREATE INDEX "SmsReceiptAnomaly_applicationId_status_lastOccurredAt_idx" ON "SmsReceiptAnomaly"("applicationId", "status", "lastOccurredAt");
|
||||
CREATE INDEX "SmsReceiptAnomaly_channelId_status_lastOccurredAt_idx" ON "SmsReceiptAnomaly"("channelId", "status", "lastOccurredAt");
|
||||
CREATE INDEX "SmsReceiptAnomaly_messageRecordId_idx" ON "SmsReceiptAnomaly"("messageRecordId");
|
||||
CREATE INDEX "SmsReceiptAnomaly_submitRecordId_idx" ON "SmsReceiptAnomaly"("submitRecordId");
|
||||
|
||||
ALTER TABLE "SmsReceiptAnomaly" ADD CONSTRAINT "SmsReceiptAnomaly_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "SmsReceiptAnomaly" ADD CONSTRAINT "SmsReceiptAnomaly_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "SmsReceiptAnomaly" ADD CONSTRAINT "SmsReceiptAnomaly_channelId_fkey" FOREIGN KEY ("channelId") REFERENCES "SmsChannel"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "SmsReceiptAnomaly" ADD CONSTRAINT "SmsReceiptAnomaly_messageRecordId_fkey" FOREIGN KEY ("messageRecordId") REFERENCES "SmsMessageRecord"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "SmsReceiptAnomaly" ADD CONSTRAINT "SmsReceiptAnomaly_submitRecordId_fkey" FOREIGN KEY ("submitRecordId") REFERENCES "SmsSubmitRecord"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "SmsReceiptAnomaly" ADD CONSTRAINT "SmsReceiptAnomaly_receiptRecordId_fkey" FOREIGN KEY ("receiptRecordId") REFERENCES "SmsReceiptRecord"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "ProtocolInteractionLog"
|
||||
ADD COLUMN "phoneNumber" TEXT;
|
||||
@@ -0,0 +1,203 @@
|
||||
ALTER TABLE "SmsChannel"
|
||||
ADD COLUMN "carriers" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[];
|
||||
|
||||
UPDATE "SmsChannel"
|
||||
SET "carriers" = CASE
|
||||
WHEN "carrier" = 'mobile' THEN ARRAY['mobile']::TEXT[]
|
||||
WHEN "carrier" = 'unicom' THEN ARRAY['unicom']::TEXT[]
|
||||
WHEN "carrier" = 'telecom' THEN ARRAY['telecom']::TEXT[]
|
||||
WHEN "carrier" = 'all' THEN ARRAY['mobile', 'unicom', 'telecom']::TEXT[]
|
||||
-- 旧页面和旧发送链对空/未知carrier一直按移动处理,迁移保持原业务语义且保证至少一项。
|
||||
ELSE ARRAY['mobile']::TEXT[]
|
||||
END;
|
||||
|
||||
ALTER TABLE "SmsChannel"
|
||||
ADD CONSTRAINT "SmsChannel_carriers_supported_check"
|
||||
CHECK (
|
||||
cardinality("carriers") BETWEEN 1 AND 3
|
||||
AND "carriers" <@ ARRAY['mobile', 'unicom', 'telecom']::TEXT[]
|
||||
);
|
||||
|
||||
ALTER TABLE "ChannelSignatureReportTask"
|
||||
ADD COLUMN "carrier" TEXT,
|
||||
ADD COLUMN "approvedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "approvalScope" TEXT NOT NULL DEFAULT 'legacy_channel';
|
||||
|
||||
-- The current approved timestamp is reconstructed from the latest transition
|
||||
-- into approved. updatedAt is deliberately not used because unrelated edits
|
||||
-- can change it and would incorrectly restart the grace period.
|
||||
UPDATE "ChannelSignatureReportTask" task
|
||||
SET "approvedAt" = approved_record."approvedAt"
|
||||
FROM (
|
||||
SELECT "taskId", MAX("createdAt") AS "approvedAt"
|
||||
FROM "ChannelSignatureReportRecord"
|
||||
WHERE "statusAfter" = 'approved'
|
||||
GROUP BY "taskId"
|
||||
) approved_record
|
||||
WHERE task.id = approved_record."taskId"
|
||||
AND task.status = 'approved';
|
||||
|
||||
CREATE INDEX "ChannelSignatureReportTask_signatureId_channelId_carrier_idx"
|
||||
ON "ChannelSignatureReportTask"("signatureId", "channelId", "carrier");
|
||||
|
||||
-- 旧索引把运营商排除在唯一维度外,会阻止同一签名/通道建立多运营商事实。
|
||||
-- 拆成三类条件索引,在升级维度的同时继续保护历史任务和引流任务不重复。
|
||||
DROP INDEX IF EXISTS "ChannelSignatureReportTask_target_key";
|
||||
|
||||
CREATE UNIQUE INDEX "ChannelSignatureReportTask_signature_channel_carrier_key"
|
||||
ON "ChannelSignatureReportTask"("signatureId", "channelId", "carrier")
|
||||
WHERE "reportType" = 'signature' AND "carrier" IS NOT NULL AND "drainageItemId" IS NULL;
|
||||
|
||||
CREATE UNIQUE INDEX "ChannelSignatureReportTask_legacy_signature_channel_key"
|
||||
ON "ChannelSignatureReportTask"("signatureId", "channelId")
|
||||
WHERE "reportType" = 'signature' AND "carrier" IS NULL AND "drainageItemId" IS NULL;
|
||||
|
||||
CREATE UNIQUE INDEX "ChannelSignatureReportTask_drainage_target_key"
|
||||
ON "ChannelSignatureReportTask"("signatureId", "drainageItemId", "channelId")
|
||||
WHERE "reportType" = 'drainage' AND "drainageItemId" IS NOT NULL;
|
||||
|
||||
CREATE TABLE "SignatureRetirementRule" (
|
||||
"id" TEXT NOT NULL,
|
||||
"ruleType" TEXT NOT NULL,
|
||||
"targetId" TEXT,
|
||||
"targetKey" TEXT NOT NULL DEFAULT '',
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"mobileWindowDays" INTEGER NOT NULL DEFAULT 30,
|
||||
"mobileThreshold" INTEGER NOT NULL DEFAULT 1,
|
||||
"unicomWindowDays" INTEGER NOT NULL DEFAULT 30,
|
||||
"unicomThreshold" INTEGER NOT NULL DEFAULT 1,
|
||||
"telecomWindowDays" INTEGER NOT NULL DEFAULT 30,
|
||||
"telecomThreshold" INTEGER NOT NULL DEFAULT 1,
|
||||
"messageTemplate" TEXT,
|
||||
"version" INTEGER NOT NULL DEFAULT 1,
|
||||
"createdById" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "SignatureRetirementRule_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE UNIQUE INDEX "SignatureRetirementRule_ruleType_targetKey_key" ON "SignatureRetirementRule"("ruleType", "targetKey");
|
||||
CREATE INDEX "SignatureRetirementRule_ruleType_enabled_idx" ON "SignatureRetirementRule"("ruleType", "enabled");
|
||||
|
||||
CREATE TABLE "SignatureRetirementWebhook" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"platform" TEXT NOT NULL,
|
||||
"urlEncrypted" TEXT NOT NULL,
|
||||
"urlMasked" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'active',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "SignatureRetirementWebhook_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE INDEX "SignatureRetirementWebhook_status_createdAt_idx" ON "SignatureRetirementWebhook"("status", "createdAt");
|
||||
|
||||
CREATE TABLE "SignatureRetirementCycle" (
|
||||
"id" TEXT NOT NULL,
|
||||
"dimensionType" TEXT NOT NULL,
|
||||
"signatureId" TEXT NOT NULL,
|
||||
"channelId" TEXT,
|
||||
"channelKey" TEXT NOT NULL DEFAULT '',
|
||||
"carrier" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'open',
|
||||
"startedOn" DATE NOT NULL,
|
||||
"lastDetectedOn" DATE NOT NULL,
|
||||
"resolvedOn" DATE,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "SignatureRetirementCycle_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE INDEX "SignatureRetirementCycle_dimension_status_idx" ON "SignatureRetirementCycle"("dimensionType", "signatureId", "channelKey", "carrier", "status");
|
||||
CREATE INDEX "SignatureRetirementCycle_status_lastDetectedOn_idx" ON "SignatureRetirementCycle"("status", "lastDetectedOn");
|
||||
-- 同一监控维度只能存在一个开放周期,数据库约束用于兜住并发检测实例。
|
||||
CREATE UNIQUE INDEX "SignatureRetirementCycle_open_dimension_key"
|
||||
ON "SignatureRetirementCycle"("dimensionType", "signatureId", "channelKey", "carrier")
|
||||
WHERE "status" = 'open';
|
||||
|
||||
CREATE TABLE "SignatureRetirementDetection" (
|
||||
"id" TEXT NOT NULL,
|
||||
"detectionDate" DATE NOT NULL,
|
||||
"dimensionType" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"applicationId" TEXT,
|
||||
"signatureId" TEXT NOT NULL,
|
||||
"channelId" TEXT,
|
||||
"channelKey" TEXT NOT NULL DEFAULT '',
|
||||
"carrier" TEXT NOT NULL,
|
||||
"windowDays" INTEGER NOT NULL,
|
||||
"threshold" INTEGER NOT NULL,
|
||||
"submittedAttempts" INTEGER NOT NULL DEFAULT 0,
|
||||
"acceptedBusinessCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"deliveredBusinessCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"approvedAt" TIMESTAMP(3) NOT NULL,
|
||||
"ruleId" TEXT,
|
||||
"ruleVersion" INTEGER NOT NULL DEFAULT 1,
|
||||
"status" TEXT NOT NULL,
|
||||
"cycleId" TEXT,
|
||||
"suppressed" BOOLEAN NOT NULL DEFAULT false,
|
||||
"notificationTitle" TEXT,
|
||||
"notificationContent" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "SignatureRetirementDetection_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE UNIQUE INDEX "SignatureRetirementDetection_dimension_key" ON "SignatureRetirementDetection"("detectionDate", "dimensionType", "signatureId", "channelKey", "carrier");
|
||||
CREATE INDEX "SignatureRetirementDetection_date_type_status_idx" ON "SignatureRetirementDetection"("detectionDate", "dimensionType", "status");
|
||||
CREATE INDEX "SignatureRetirementDetection_signature_carrier_date_idx" ON "SignatureRetirementDetection"("signatureId", "carrier", "detectionDate");
|
||||
CREATE INDEX "SignatureRetirementDetection_channel_carrier_date_idx" ON "SignatureRetirementDetection"("channelId", "carrier", "detectionDate");
|
||||
|
||||
CREATE TABLE "SignatureRetirementMessage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"detectionId" TEXT NOT NULL,
|
||||
"cycleId" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"isRead" BOOLEAN NOT NULL DEFAULT false,
|
||||
"suppressed" BOOLEAN NOT NULL DEFAULT false,
|
||||
"readAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "SignatureRetirementMessage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE UNIQUE INDEX "SignatureRetirementMessage_detectionId_key" ON "SignatureRetirementMessage"("detectionId");
|
||||
CREATE INDEX "SignatureRetirementMessage_created_read_suppressed_idx" ON "SignatureRetirementMessage"("createdAt", "isRead", "suppressed");
|
||||
CREATE INDEX "SignatureRetirementMessage_tenant_createdAt_idx" ON "SignatureRetirementMessage"("tenantId", "createdAt");
|
||||
|
||||
CREATE TABLE "SignatureRetirementSuppression" (
|
||||
"id" TEXT NOT NULL,
|
||||
"dimensionType" TEXT NOT NULL,
|
||||
"signatureId" TEXT NOT NULL,
|
||||
"channelId" TEXT,
|
||||
"channelKey" TEXT NOT NULL DEFAULT '',
|
||||
"carrier" TEXT NOT NULL,
|
||||
"mode" TEXT NOT NULL,
|
||||
"muteUntil" DATE,
|
||||
"active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"reason" TEXT,
|
||||
"operatorId" TEXT,
|
||||
"cancelledAt" TIMESTAMP(3),
|
||||
"cancelledById" TEXT,
|
||||
"cancelReason" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "SignatureRetirementSuppression_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE UNIQUE INDEX "SignatureRetirementSuppression_dimension_key" ON "SignatureRetirementSuppression"("dimensionType", "signatureId", "channelKey", "carrier");
|
||||
CREATE INDEX "SignatureRetirementSuppression_active_muteUntil_idx" ON "SignatureRetirementSuppression"("active", "muteUntil");
|
||||
|
||||
CREATE TABLE "SignatureRetirementWebhookDelivery" (
|
||||
"id" TEXT NOT NULL,
|
||||
"detectionDate" DATE NOT NULL,
|
||||
"webhookId" TEXT NOT NULL,
|
||||
"groupKey" TEXT NOT NULL,
|
||||
"payload" JSONB NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'pending',
|
||||
"attemptCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"nextRetryAt" TIMESTAMP(3),
|
||||
"lastHttpStatus" INTEGER,
|
||||
"lastError" TEXT,
|
||||
"deliveredAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "SignatureRetirementWebhookDelivery_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE UNIQUE INDEX "SignatureRetirementWebhookDelivery_key" ON "SignatureRetirementWebhookDelivery"("webhookId", "detectionDate", "groupKey");
|
||||
CREATE INDEX "SignatureRetirementWebhookDelivery_status_retry_idx" ON "SignatureRetirementWebhookDelivery"("status", "nextRetryAt");
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
-- 最终业务口径不再保留历史人工确认:旧通道级状态按通道能力一次性形成运营商级事实。
|
||||
-- 已有运营商任务代表更新的事实,必须保留且不得被旧任务覆盖。
|
||||
WITH legacy_targets AS (
|
||||
SELECT
|
||||
legacy.id AS "legacyId",
|
||||
legacy."tenantId",
|
||||
legacy."signatureId",
|
||||
legacy."channelId",
|
||||
legacy.status,
|
||||
legacy.reason,
|
||||
legacy."createdById",
|
||||
supported.carrier
|
||||
FROM "ChannelSignatureReportTask" legacy
|
||||
JOIN "SmsChannel" channel ON channel.id = legacy."channelId"
|
||||
CROSS JOIN LATERAL unnest(
|
||||
CASE
|
||||
WHEN cardinality(channel.carriers) > 0 THEN channel.carriers
|
||||
WHEN channel.carrier = 'all' THEN ARRAY['mobile', 'unicom', 'telecom']::TEXT[]
|
||||
WHEN channel.carrier IN ('mobile', 'unicom', 'telecom') THEN ARRAY[channel.carrier]::TEXT[]
|
||||
ELSE ARRAY['mobile']::TEXT[]
|
||||
END
|
||||
) AS supported(carrier)
|
||||
WHERE legacy."reportType" = 'signature'
|
||||
AND legacy."drainageItemId" IS NULL
|
||||
AND legacy.carrier IS NULL
|
||||
AND legacy."approvalScope" = 'legacy_channel'
|
||||
), inserted_tasks AS (
|
||||
INSERT INTO "ChannelSignatureReportTask" (
|
||||
id,
|
||||
"tenantId",
|
||||
"signatureId",
|
||||
"channelId",
|
||||
carrier,
|
||||
"approvedAt",
|
||||
"approvalScope",
|
||||
"reportType",
|
||||
"drainageItemId",
|
||||
status,
|
||||
reason,
|
||||
"createdById",
|
||||
"createdAt",
|
||||
"updatedAt"
|
||||
)
|
||||
SELECT
|
||||
'legacy-auto-' || md5(target."legacyId" || ':' || target.carrier),
|
||||
target."tenantId",
|
||||
target."signatureId",
|
||||
target."channelId",
|
||||
target.carrier,
|
||||
CASE WHEN target.status = 'approved' THEN CURRENT_TIMESTAMP ELSE NULL END,
|
||||
'carrier_specific',
|
||||
'signature',
|
||||
NULL,
|
||||
target.status,
|
||||
target.reason,
|
||||
target."createdById",
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM legacy_targets target
|
||||
ON CONFLICT DO NOTHING
|
||||
RETURNING id, "channelId", status
|
||||
)
|
||||
INSERT INTO "ChannelSignatureReportRecord" (
|
||||
id,
|
||||
"taskId",
|
||||
"channelId",
|
||||
action,
|
||||
"statusBefore",
|
||||
"statusAfter",
|
||||
reason,
|
||||
"operatorId",
|
||||
"sourceEntry",
|
||||
"createdAt"
|
||||
)
|
||||
SELECT
|
||||
'legacy-auto-record-' || md5(task.id),
|
||||
task.id,
|
||||
task."channelId",
|
||||
'legacy_carrier_auto_split',
|
||||
NULL,
|
||||
task.status,
|
||||
'历史通道级任务按通道运营商能力自动转换',
|
||||
NULL,
|
||||
'migration',
|
||||
CURRENT_TIMESTAMP
|
||||
FROM inserted_tasks task
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
WITH legacy_targets AS (
|
||||
SELECT
|
||||
legacy.id AS "legacyId",
|
||||
legacy."signatureId",
|
||||
legacy."channelId",
|
||||
supported.carrier
|
||||
FROM "ChannelSignatureReportTask" legacy
|
||||
JOIN "SmsChannel" channel ON channel.id = legacy."channelId"
|
||||
CROSS JOIN LATERAL unnest(
|
||||
CASE
|
||||
WHEN cardinality(channel.carriers) > 0 THEN channel.carriers
|
||||
WHEN channel.carrier = 'all' THEN ARRAY['mobile', 'unicom', 'telecom']::TEXT[]
|
||||
WHEN channel.carrier IN ('mobile', 'unicom', 'telecom') THEN ARRAY[channel.carrier]::TEXT[]
|
||||
ELSE ARRAY['mobile']::TEXT[]
|
||||
END
|
||||
) AS supported(carrier)
|
||||
WHERE legacy."reportType" = 'signature'
|
||||
AND legacy."drainageItemId" IS NULL
|
||||
AND legacy.carrier IS NULL
|
||||
AND legacy."approvalScope" = 'legacy_channel'
|
||||
), completed_legacy AS (
|
||||
SELECT target."legacyId"
|
||||
FROM legacy_targets target
|
||||
LEFT JOIN "ChannelSignatureReportTask" exact
|
||||
ON exact."signatureId" = target."signatureId"
|
||||
AND exact."channelId" = target."channelId"
|
||||
AND exact."reportType" = 'signature'
|
||||
AND exact."drainageItemId" IS NULL
|
||||
AND exact.carrier = target.carrier
|
||||
GROUP BY target."legacyId"
|
||||
HAVING COUNT(DISTINCT target.carrier) = COUNT(DISTINCT exact.carrier)
|
||||
), updated_legacy AS (
|
||||
UPDATE "ChannelSignatureReportTask" legacy
|
||||
SET "approvalScope" = 'legacy_split',
|
||||
"updatedAt" = CURRENT_TIMESTAMP
|
||||
FROM completed_legacy completed
|
||||
WHERE legacy.id = completed."legacyId"
|
||||
AND legacy."approvalScope" = 'legacy_channel'
|
||||
RETURNING legacy.id, legacy."channelId", legacy.status
|
||||
)
|
||||
INSERT INTO "ChannelSignatureReportRecord" (
|
||||
id,
|
||||
"taskId",
|
||||
"channelId",
|
||||
action,
|
||||
"statusBefore",
|
||||
"statusAfter",
|
||||
reason,
|
||||
"operatorId",
|
||||
"sourceEntry",
|
||||
"createdAt"
|
||||
)
|
||||
SELECT
|
||||
'legacy-split-record-' || md5(legacy.id),
|
||||
legacy.id,
|
||||
legacy."channelId",
|
||||
'legacy_scope_auto_split',
|
||||
legacy.status,
|
||||
legacy.status,
|
||||
'全部适用运营商已自动形成独立报备任务',
|
||||
NULL,
|
||||
'migration',
|
||||
CURRENT_TIMESTAMP
|
||||
FROM updated_legacy legacy
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
@@ -0,0 +1,57 @@
|
||||
CREATE TABLE "DownstreamRequeueTask" (
|
||||
"id" TEXT NOT NULL,
|
||||
"taskNo" TEXT NOT NULL,
|
||||
"tenantId" TEXT,
|
||||
"applicationId" TEXT,
|
||||
"status" TEXT NOT NULL DEFAULT 'queued',
|
||||
"filterSnapshot" JSONB NOT NULL,
|
||||
"snapshotAt" TIMESTAMP(3) NOT NULL,
|
||||
"reason" TEXT NOT NULL,
|
||||
"ratePerSecond" INTEGER NOT NULL DEFAULT 10,
|
||||
"consecutiveFailureLimit" INTEGER NOT NULL DEFAULT 10,
|
||||
"totalCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"successCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"failedCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"skippedCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"waitingCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"consecutiveFailures" INTEGER NOT NULL DEFAULT 0,
|
||||
"lastError" TEXT,
|
||||
"createdById" TEXT,
|
||||
"startedAt" TIMESTAMP(3),
|
||||
"pausedAt" TIMESTAMP(3),
|
||||
"finishedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "DownstreamRequeueTask_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "DownstreamRequeueTaskItem" (
|
||||
"id" TEXT NOT NULL,
|
||||
"taskId" TEXT NOT NULL,
|
||||
"deliveryId" TEXT NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'queued',
|
||||
"previousStatus" TEXT NOT NULL,
|
||||
"skipReason" TEXT,
|
||||
"errorMessage" TEXT,
|
||||
"claimedAt" TIMESTAMP(3),
|
||||
"completedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "DownstreamRequeueTaskItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "DownstreamRequeueTask_taskNo_key" ON "DownstreamRequeueTask"("taskNo");
|
||||
CREATE INDEX "DownstreamRequeueTask_status_createdAt_idx" ON "DownstreamRequeueTask"("status", "createdAt");
|
||||
CREATE INDEX "DownstreamRequeueTask_applicationId_status_createdAt_idx" ON "DownstreamRequeueTask"("applicationId", "status", "createdAt");
|
||||
CREATE INDEX "DownstreamRequeueTask_tenantId_createdAt_idx" ON "DownstreamRequeueTask"("tenantId", "createdAt");
|
||||
CREATE UNIQUE INDEX "DownstreamRequeueTaskItem_taskId_deliveryId_key" ON "DownstreamRequeueTaskItem"("taskId", "deliveryId");
|
||||
CREATE INDEX "DownstreamRequeueTaskItem_taskId_status_createdAt_idx" ON "DownstreamRequeueTaskItem"("taskId", "status", "createdAt");
|
||||
CREATE INDEX "DownstreamRequeueTaskItem_applicationId_status_createdAt_idx" ON "DownstreamRequeueTaskItem"("applicationId", "status", "createdAt");
|
||||
CREATE INDEX "DownstreamRequeueTaskItem_deliveryId_status_idx" ON "DownstreamRequeueTaskItem"("deliveryId", "status");
|
||||
|
||||
ALTER TABLE "DownstreamRequeueTask" ADD CONSTRAINT "DownstreamRequeueTask_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "DownstreamRequeueTask" ADD CONSTRAINT "DownstreamRequeueTask_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "DownstreamRequeueTask" ADD CONSTRAINT "DownstreamRequeueTask_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "DownstreamRequeueTaskItem" ADD CONSTRAINT "DownstreamRequeueTaskItem_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "DownstreamRequeueTask"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "DownstreamRequeueTaskItem" ADD CONSTRAINT "DownstreamRequeueTaskItem_deliveryId_fkey" FOREIGN KEY ("deliveryId") REFERENCES "CmppDownstreamDelivery"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user