Compare commits
319
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||||
+13
@@ -2,12 +2,25 @@ node_modules/
|
|||||||
dist/
|
dist/
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
!.env.example
|
||||||
*.local
|
*.local
|
||||||
|
.local-tools/
|
||||||
|
.local-data/
|
||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
|
logs/
|
||||||
|
coverage/
|
||||||
|
gateway/gateway.exe
|
||||||
|
dump.rdb
|
||||||
|
*.tsbuildinfo
|
||||||
|
outputs/
|
||||||
|
tmp_*.py
|
||||||
|
tmp_*.ps1
|
||||||
|
tmp_*.sh
|
||||||
|
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
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,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;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
ALTER TABLE "DownstreamRequeueTask"
|
||||||
|
ADD COLUMN "applicationFailures" JSONB NOT NULL DEFAULT '{}',
|
||||||
|
ADD COLUMN "scanLeaseOwner" TEXT,
|
||||||
|
ADD COLUMN "scanLeaseUntil" TIMESTAMP(3);
|
||||||
|
|
||||||
|
CREATE TABLE "DownstreamRequeueRateWindow" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"applicationId" TEXT NOT NULL,
|
||||||
|
"windowStartedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"consumed" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT "DownstreamRequeueRateWindow_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "DownstreamRequeueRateWindow_applicationId_windowStartedAt_key"
|
||||||
|
ON "DownstreamRequeueRateWindow"("applicationId", "windowStartedAt");
|
||||||
|
CREATE INDEX "DownstreamRequeueRateWindow_windowStartedAt_idx"
|
||||||
|
ON "DownstreamRequeueRateWindow"("windowStartedAt");
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
CREATE TABLE "SecurityDetectionRule" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"code" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"sourceType" TEXT NOT NULL,
|
||||||
|
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"threshold" INTEGER NOT NULL,
|
||||||
|
"windowSeconds" INTEGER NOT NULL,
|
||||||
|
"cooldownSeconds" INTEGER NOT NULL,
|
||||||
|
"severity" TEXT NOT NULL,
|
||||||
|
"defaultBlockSeconds" INTEGER NOT NULL,
|
||||||
|
"maximumBlockSeconds" INTEGER NOT NULL,
|
||||||
|
"configVersion" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"effectiveVersion" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"applyStatus" TEXT NOT NULL DEFAULT 'pending',
|
||||||
|
"lastApplyError" TEXT,
|
||||||
|
"pendingConfig" JSONB,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
CONSTRAINT "SecurityDetectionRule_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
CREATE TABLE "SecurityDetectionEvent" (
|
||||||
|
"id" TEXT NOT NULL, "eventKey" TEXT NOT NULL, "ruleId" TEXT NOT NULL,
|
||||||
|
"sourceIp" TEXT NOT NULL, "sourcePort" INTEGER, "accountHash" TEXT,
|
||||||
|
"path" TEXT, "protocol" TEXT, "resultCode" TEXT, "evidence" JSONB,
|
||||||
|
"occurredAt" TIMESTAMP(3) NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT "SecurityDetectionEvent_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
CREATE TABLE "SecurityAlert" (
|
||||||
|
"id" TEXT NOT NULL, "fingerprint" TEXT NOT NULL, "ruleId" TEXT NOT NULL,
|
||||||
|
"sourceIp" TEXT NOT NULL, "severity" TEXT NOT NULL, "status" TEXT NOT NULL DEFAULT 'open',
|
||||||
|
"eventCount" INTEGER NOT NULL DEFAULT 0, "windowStartedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"firstOccurredAt" TIMESTAMP(3) NOT NULL, "lastOccurredAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"acknowledgedAt" TIMESTAMP(3), "acknowledgedById" TEXT, "ignoredAt" TIMESTAMP(3),
|
||||||
|
"ignoredById" TEXT, "ignoreReason" TEXT, "blockId" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
CONSTRAINT "SecurityAlert_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
CREATE TABLE "SecurityBlock" (
|
||||||
|
"id" TEXT NOT NULL, "operationKey" TEXT NOT NULL, "alertId" TEXT, "sourceIp" TEXT NOT NULL,
|
||||||
|
"executor" TEXT NOT NULL, "status" TEXT NOT NULL DEFAULT 'requested', "durationSeconds" INTEGER NOT NULL,
|
||||||
|
"reason" TEXT NOT NULL, "requestedById" TEXT NOT NULL, "requestedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"appliedAt" TIMESTAMP(3), "expiresAt" TIMESTAMP(3), "releasedAt" TIMESTAMP(3), "releasedById" TEXT,
|
||||||
|
"executorReference" TEXT, "lastError" TEXT, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL, CONSTRAINT "SecurityBlock_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
CREATE TABLE "SecurityProtectedNetwork" (
|
||||||
|
"id" TEXT NOT NULL, "network" TEXT NOT NULL, "name" TEXT NOT NULL, "reason" TEXT NOT NULL,
|
||||||
|
"enabled" BOOLEAN NOT NULL DEFAULT true, "createdById" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
CONSTRAINT "SecurityProtectedNetwork_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX "SecurityDetectionRule_code_key" ON "SecurityDetectionRule"("code");
|
||||||
|
CREATE INDEX "SecurityDetectionRule_enabled_sourceType_idx" ON "SecurityDetectionRule"("enabled", "sourceType");
|
||||||
|
CREATE UNIQUE INDEX "SecurityDetectionEvent_eventKey_key" ON "SecurityDetectionEvent"("eventKey");
|
||||||
|
CREATE INDEX "SecurityDetectionEvent_ruleId_occurredAt_idx" ON "SecurityDetectionEvent"("ruleId", "occurredAt");
|
||||||
|
CREATE INDEX "SecurityDetectionEvent_sourceIp_occurredAt_idx" ON "SecurityDetectionEvent"("sourceIp", "occurredAt");
|
||||||
|
CREATE UNIQUE INDEX "SecurityAlert_fingerprint_key" ON "SecurityAlert"("fingerprint");
|
||||||
|
CREATE INDEX "SecurityAlert_status_severity_lastOccurredAt_idx" ON "SecurityAlert"("status", "severity", "lastOccurredAt");
|
||||||
|
CREATE INDEX "SecurityAlert_sourceIp_status_lastOccurredAt_idx" ON "SecurityAlert"("sourceIp", "status", "lastOccurredAt");
|
||||||
|
CREATE INDEX "SecurityAlert_ruleId_status_lastOccurredAt_idx" ON "SecurityAlert"("ruleId", "status", "lastOccurredAt");
|
||||||
|
CREATE UNIQUE INDEX "SecurityBlock_operationKey_key" ON "SecurityBlock"("operationKey");
|
||||||
|
CREATE INDEX "SecurityBlock_status_expiresAt_idx" ON "SecurityBlock"("status", "expiresAt");
|
||||||
|
CREATE INDEX "SecurityBlock_sourceIp_status_requestedAt_idx" ON "SecurityBlock"("sourceIp", "status", "requestedAt");
|
||||||
|
CREATE INDEX "SecurityBlock_alertId_idx" ON "SecurityBlock"("alertId");
|
||||||
|
CREATE UNIQUE INDEX "SecurityProtectedNetwork_network_key" ON "SecurityProtectedNetwork"("network");
|
||||||
|
CREATE INDEX "SecurityProtectedNetwork_enabled_createdAt_idx" ON "SecurityProtectedNetwork"("enabled", "createdAt");
|
||||||
|
ALTER TABLE "SecurityDetectionEvent" ADD CONSTRAINT "SecurityDetectionEvent_ruleId_fkey" FOREIGN KEY ("ruleId") REFERENCES "SecurityDetectionRule"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
ALTER TABLE "SecurityAlert" ADD CONSTRAINT "SecurityAlert_ruleId_fkey" FOREIGN KEY ("ruleId") REFERENCES "SecurityDetectionRule"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
INSERT INTO "SecurityDetectionRule" ("id", "code", "name", "sourceType", "threshold", "windowSeconds", "cooldownSeconds", "severity", "defaultBlockSeconds", "maximumBlockSeconds", "configVersion", "effectiveVersion", "applyStatus", "updatedAt") VALUES
|
||||||
|
('sec_admin_login', 'admin_login_failure', '运营端登录失败', 'application', 8, 600, 900, 'medium', 3600, 604800, 1, 1, 'effective', CURRENT_TIMESTAMP),
|
||||||
|
('sec_client_login', 'client_login_failure', '客户端登录失败', 'application', 8, 600, 900, 'medium', 3600, 604800, 1, 1, 'effective', CURRENT_TIMESTAMP),
|
||||||
|
('sec_ssh_auth', 'ssh_auth_failure', 'SSH认证失败', 'fail2ban', 6, 600, 1800, 'high', 86400, 604800, 1, 1, 'effective', CURRENT_TIMESTAMP),
|
||||||
|
('sec_cmpp_auth', 'cmpp_auth_failure', 'CMPP认证失败', 'gateway', 5, 300, 900, 'high', 3600, 604800, 1, 1, 'effective', CURRENT_TIMESTAMP),
|
||||||
|
('sec_cmpp_abuse', 'cmpp_protocol_abuse', 'CMPP协议滥用', 'gateway', 20, 60, 900, 'critical', 3600, 604800, 1, 1, 'effective', CURRENT_TIMESTAMP),
|
||||||
|
('sec_http_key', 'http_invalid_api_key', 'HTTP错误密钥', 'application', 10, 300, 900, 'high', 3600, 604800, 1, 1, 'effective', CURRENT_TIMESTAMP),
|
||||||
|
('sec_http_sign', 'http_signature_failure', 'HTTP签名错误', 'application', 10, 300, 900, 'high', 3600, 604800, 1, 1, 'effective', CURRENT_TIMESTAMP),
|
||||||
|
('sec_http_replay', 'http_replay_attempt', 'HTTP重放尝试', 'application', 3, 600, 1800, 'critical', 86400, 604800, 1, 1, 'effective', CURRENT_TIMESTAMP),
|
||||||
|
('sec_http_scan', 'http_malicious_scan', 'HTTP恶意扫描', 'fail2ban', 20, 60, 900, 'high', 3600, 604800, 1, 1, 'effective', CURRENT_TIMESTAMP);
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
CREATE TABLE "InfrastructureAlertSetting" (
|
||||||
|
"id" TEXT NOT NULL DEFAULT 'global',
|
||||||
|
"configVersion" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"effectiveVersion" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"thresholds" JSONB NOT NULL,
|
||||||
|
"effectiveThresholds" JSONB NOT NULL,
|
||||||
|
"applyStatus" TEXT NOT NULL DEFAULT 'effective',
|
||||||
|
"lastError" TEXT,
|
||||||
|
"updatedById" TEXT,
|
||||||
|
"appliedAt" TIMESTAMP(3),
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT "InfrastructureAlertSetting_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO "InfrastructureAlertSetting" (
|
||||||
|
"id", "thresholds", "effectiveThresholds", "appliedAt"
|
||||||
|
) VALUES (
|
||||||
|
'global',
|
||||||
|
'{"hostCpu":{"warning":80,"critical":90},"hostMemory":{"warning":85,"critical":95},"hostDisk":{"warning":80,"critical":90},"apiError":{"warning":1,"critical":5},"apiLatency":{"warning":1,"critical":3},"apiEventLoop":{"warning":0.2,"critical":1},"gatewayQueue":{"warning":30,"critical":120},"postgresConnections":{"warning":70,"critical":85},"redisMemory":{"warning":70,"critical":85},"minioCapacity":{"warning":80,"critical":90}}'::jsonb,
|
||||||
|
'{"hostCpu":{"warning":80,"critical":90},"hostMemory":{"warning":85,"critical":95},"hostDisk":{"warning":80,"critical":90},"apiError":{"warning":1,"critical":5},"apiLatency":{"warning":1,"critical":3},"apiEventLoop":{"warning":0.2,"critical":1},"gatewayQueue":{"warning":30,"critical":120},"postgresConnections":{"warning":70,"critical":85},"redisMemory":{"warning":70,"critical":85},"minioCapacity":{"warning":80,"critical":90}}'::jsonb,
|
||||||
|
CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user