sendi/prisma/schema.prisma
jmcqueen d0a0dda2b8 Initial commit: Sendi 2.0 SMS gateway (Webex + Twilio)
Sendi is a Webex to Twilio SMS gateway written in TypeScript that runs
directly on Node 22.18+ via native type-stripping (no bundler/transpiler).
Each Webex space represents one external SMS contact; inbound MMS is
proxied both ways, with delivery-status cards for outbound sends.

Highlights:
- Express 5 HTTP surface for Twilio /sms and /callback (signature-validated)
- webex-node-bot-framework in websocket transport mode for bot control plane
- Prisma 6 + SQLite via better-sqlite3 driver adapter
- Zod-validated env with fail-fast startup and pino secret redaction
- Pino logging: pretty in dev, structured JSON in prod
- 13 vitest unit tests (phone normalization, Twilio signature validation,
  card action dispatch)
- Native --require preload patches Node 22's read-only globalThis.navigator
  so @webex/internal-media-core (transitive) can load

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 11:10:44 -04:00

108 lines
2.7 KiB
Text

generator client {
provider = "prisma-client-js"
previewFeatures = ["driverAdapters"]
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Space {
id String @id @default(uuid())
roomId String @unique
smsPhone String
wbxTmPhone String
smsName String
title String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
messages Message[]
bulkJobs BulkJob[]
surveys Survey[]
pendingMessages PendingMessage[]
@@index([smsPhone, wbxTmPhone])
}
model Contact {
id String @id @default(uuid())
name String
phone String @unique
blocked Boolean @default(false)
blockReason String?
createdAt DateTime @default(now())
}
model Message {
id String @id @default(uuid())
sid String? @unique
roomId String
from String // person email or phone
body String
mediaUrls String // JSON stringified array e.g. '["url1", "url2"]'
status String // sent, delivered, failed, undelivered, etc.
createdAt DateTime @default(now())
space Space? @relation(fields: [roomId], references: [roomId], onDelete: Cascade)
@@index([roomId])
@@index([createdAt])
}
model BulkJob {
id String @id @default(uuid())
parentId String
senderEmail String
text String
files String // JSON stringified array of file URLs/paths
status String @default("pending")
analytics Json?
sentAt DateTime?
completedAt DateTime?
spaceId String?
space Space? @relation(fields: [spaceId], references: [id], onDelete: Cascade)
}
model Survey {
id String @id @default(uuid())
roomId String
question String
endDate DateTime
responses String // JSON stringified array of {phone, response}
completed Boolean @default(false)
space Space? @relation(fields: [roomId], references: [roomId], onDelete: Cascade)
}
model OutboundRouting {
email String @id
phoneNumber String
groups String // JSON stringified array e.g. '["ottawa", "hazleton"]'
}
model InboundRouting {
phoneNumber String @id
site String
people String // JSON stringified array
keywords Json?
outOfOffice Json?
}
model BlockNumber {
phoneNumber String @id
name String
reason String
createdAt DateTime @default(now())
}
// Tracks SMS sends that are waiting for the user to upload an attachment in Webex.
// Used by the PendingMessageStore service. One row per room (overwrites previous).
model PendingMessage {
roomId String @id
text String
createdAt DateTime @default(now())
space Space? @relation(fields: [roomId], references: [roomId], onDelete: Cascade)
}