Compare commits
218
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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,45 @@
|
||||
# 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
|
||||
API_ENABLE_SEND_WORKER=true
|
||||
API_SEND_WORKER_CONCURRENCY=50
|
||||
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
|
||||
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
|
||||
CMPP_DOWNSTREAM_ACK_TIMEOUT_SECONDS=30
|
||||
@@ -0,0 +1 @@
|
||||
*.sh text eol=lf
|
||||
@@ -2,12 +2,18 @@ node_modules/
|
||||
dist/
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.local
|
||||
.local-tools/
|
||||
.local-data/
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
logs/
|
||||
gateway/gateway.exe
|
||||
dump.rdb
|
||||
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
@@ -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,12 @@
|
||||
/** @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,
|
||||
};
|
||||
Generated
+9533
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "cmpp-platform-api",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.build.json",
|
||||
"test": "jest --runInBand",
|
||||
"test:watch": "jest --watch",
|
||||
"start": "node dist/main.js",
|
||||
"start:dev": "ts-node src/main.ts",
|
||||
"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.2.3",
|
||||
"@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",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'prisma/config';
|
||||
|
||||
export default defineConfig({
|
||||
schema: 'prisma/schema.prisma',
|
||||
migrations: {
|
||||
path: 'prisma/migrations',
|
||||
},
|
||||
datasource: {
|
||||
url:
|
||||
process.env.DATABASE_URL ??
|
||||
'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
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
CREATE TABLE "InfrastructureAlertRead" (
|
||||
"id" TEXT NOT NULL,
|
||||
"fingerprint" TEXT NOT NULL,
|
||||
"activeAt" TIMESTAMP(3) NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"readAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "InfrastructureAlertRead_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "InfrastructureAlertRead_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "InfrastructureAlertRead_fingerprint_userId_key"
|
||||
ON "InfrastructureAlertRead"("fingerprint", "userId");
|
||||
|
||||
CREATE INDEX "InfrastructureAlertRead_userId_readAt_idx"
|
||||
ON "InfrastructureAlertRead"("userId", "readAt");
|
||||
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user