feat: add HTTP API and complete client workflows

This commit is contained in:
hectorzhao
2026-07-16 11:34:06 +08:00
parent 4f07b331e5
commit dcb6162dcf
40 changed files with 2548 additions and 365 deletions
@@ -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;
+178
View File
@@ -45,6 +45,8 @@ model Tenant {
cmppConnectionStates CmppConnectionState[]
gatewayDownstreamRecoveryStatuses GatewayDownstreamRecoveryStatus[]
gatewaySubmitDeadLetters GatewaySubmitDeadLetter[]
openApiRequests OpenApiRequest[]
httpWebhookEvents HttpWebhookEvent[]
}
model EnterpriseCertification {
@@ -391,6 +393,12 @@ model SmsApplication {
connectionStates CmppConnectionState[]
gatewayDownstreamRecoveryStatuses GatewayDownstreamRecoveryStatus[]
gatewaySubmitDeadLetters GatewaySubmitDeadLetter[]
httpConfig SmsApplicationHttpConfig?
httpIpAllowlist SmsApplicationHttpIpAllowlist[]
httpApiCredentials HttpApiCredential[]
openApiRequests OpenApiRequest[]
httpWebhookEndpoints HttpWebhookEndpoint[]
httpWebhookEvents HttpWebhookEvent[]
@@index([tenantId, status])
}
@@ -407,6 +415,174 @@ model SmsApplicationIpAllowlist {
@@unique([applicationId, ipCidr])
}
model SmsApplicationHttpConfig {
id String @id @default(cuid())
applicationId String @unique
enabled Boolean @default(false)
sendEnabled Boolean @default(false)
messageQueryEnabled Boolean @default(false)
receiptWebhookEnabled Boolean @default(false)
uplinkWebhookEnabled Boolean @default(false)
uplinkQueryEnabled Boolean @default(false)
credentialSelfServiceEnabled Boolean @default(false)
qpsLimit Int @default(10)
timestampToleranceSeconds Int @default(300)
maxCredentialCount Int @default(2)
uplinkRetentionDays Int @default(90)
maxQueryRangeDays Int @default(31)
maxPageSize Int @default(100)
receiptDeliveryMode String @default("cmpp")
uplinkDeliveryMode String @default("cmpp")
webhookRetryEnabled Boolean @default(true)
webhookMaxAttempts Int @default(7)
webhookTimeoutSeconds Int @default(10)
requireHttps Boolean @default(true)
allowClientManualRetry Boolean @default(true)
allowClientTest Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade)
}
model SmsApplicationHttpIpAllowlist {
id String @id @default(cuid())
applicationId String
ipCidr String
remark String?
createdAt DateTime @default(now())
application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade)
@@unique([applicationId, ipCidr])
}
model HttpApiCredential {
id String @id @default(cuid())
applicationId String
name String
accessKey String @unique
secretEncrypted String
secretLast4 String
status String @default("active")
expiresAt DateTime?
lastUsedAt DateTime?
lastUsedIp String?
createdById String?
revokedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade)
requests OpenApiRequest[]
@@index([applicationId, status])
}
model OpenApiRequest {
id String @id @default(cuid())
tenantId String
applicationId String
credentialId String
requestId String @unique
idempotencyKey String
bodyHash String
clientMessageId String?
messageRecordId String?
httpStatus Int?
businessCode String?
responseBody Json?
sourceIp String?
userAgent String?
durationMs Int?
status String @default("processing")
completedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade)
credential HttpApiCredential @relation(fields: [credentialId], references: [id])
@@unique([applicationId, idempotencyKey])
@@index([applicationId, createdAt])
}
model HttpWebhookEndpoint {
id String @id @default(cuid())
applicationId String
eventType String
url String
secretEncrypted String
secretLast4 String
status String @default("active")
lastTestAt DateTime?
lastTestStatus String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade)
deliveries HttpWebhookDelivery[]
@@unique([applicationId, eventType])
}
model HttpWebhookEvent {
id String @id @default(cuid())
eventId String @unique
tenantId String
applicationId String
eventType String
messageRecordId String?
messageId String?
uplinkMessageId String?
payload Json
createdAt DateTime @default(now())
tenant Tenant @relation(fields: [tenantId], references: [id])
application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade)
deliveries HttpWebhookDelivery[]
@@index([applicationId, createdAt])
}
model HttpWebhookDelivery {
id String @id @default(cuid())
eventId String
endpointId String
status String @default("pending")
attemptCount Int @default(0)
nextRetryAt DateTime?
lastHttpStatus Int?
lastError String?
deliveredAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
event HttpWebhookEvent @relation(fields: [eventId], references: [id], onDelete: Cascade)
endpoint HttpWebhookEndpoint @relation(fields: [endpointId], references: [id], onDelete: Cascade)
attempts HttpWebhookAttempt[]
@@unique([eventId, endpointId])
@@index([status, nextRetryAt])
}
model HttpWebhookAttempt {
id String @id @default(cuid())
deliveryId String
attemptNo Int
requestHeaders Json?
responseStatus Int?
responseSummary String?
errorMessage String?
durationMs Int?
createdAt DateTime @default(now())
delivery HttpWebhookDelivery @relation(fields: [deliveryId], references: [id], onDelete: Cascade)
@@unique([deliveryId, attemptNo])
}
model SmsSignature {
id String @id @default(cuid())
tenantId String
@@ -1135,6 +1311,7 @@ model SmsMessageRecord {
drainageInfoId String?
reviewTaskId String?
messageId String @unique
clientMessageId String?
phoneNumber String
carrier String?
province String?
@@ -1181,6 +1358,7 @@ model SmsMessageRecord {
@@index([phoneNumber])
@@index([gatewayMessageId])
@@index([drainageInfoId, queuedAt])
@@unique([applicationId, clientMessageId])
}
model CmppSubmitSession {