Initial commit: SDI SaaS Platform foundation

- Complete monorepo structure with pnpm workspaces
- Prisma database schema with 20+ entities
- NestJS API with 9 core modules
- BullMQ orchestration worker
- AWS and Azure provider adapters
- Docker Compose infrastructure
- Complete documentation
This commit is contained in:
austindebest
2026-04-20 00:00:59 +01:00
commit d62468adf9
69 changed files with 10136 additions and 0 deletions

View File

@@ -0,0 +1,389 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ============================================================================
// TENANCY & IDENTITY
// ============================================================================
model Tenant {
id String @id @default(uuid())
name String
slug String @unique
status String @default("active") // active, suspended, terminated
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
users User[]
orders ServiceOrder[]
services Service[]
apiKeys ApiKey[]
webhookEndpoints WebhookEndpoint[]
providerAccounts ProviderAccount[]
invoices Invoice[]
@@map("tenants")
}
model User {
id String @id @default(uuid())
tenantId String
email String @unique
name String
status String @default("active")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
roles Role[]
orders ServiceOrder[]
@@map("users")
}
model Role {
id String @id @default(uuid())
userId String
name String // admin, operator, viewer
permissions Json @default("{}")
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("roles")
}
// ============================================================================
// PROVIDERS & CONNECTIVITY
// ============================================================================
model Provider {
id String @id @default(uuid())
name String
type String // aws, azure, carrier, exchange
status String @default("active")
metadata Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
endpoints Endpoint[]
productOfferings ProductOffering[]
orders ServiceOrder[]
accounts ProviderAccount[]
@@map("providers")
}
model ProviderAccount {
id String @id @default(uuid())
tenantId String
providerId String
accountId String
credentials Json // encrypted
status String @default("active")
metadata Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
provider Provider @relation(fields: [providerId], references: [id])
@@unique([tenantId, providerId, accountId])
@@map("provider_accounts")
}
model Endpoint {
id String @id @default(uuid())
providerId String
kind String // cloud_region, datacenter, exchange, customer_site
region String?
metro String?
location String?
status String @default("available")
metadata Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
provider Provider @relation(fields: [providerId], references: [id])
sourceOrders ServiceOrder[] @relation("SourceEndpoint")
targetOrders ServiceOrder[] @relation("TargetEndpoint")
@@map("endpoints")
}
// ============================================================================
// CATALOG & PRODUCTS
// ============================================================================
model ProductOffering {
id String @id @default(uuid())
providerId String
name String
description String?
type String // cloud_interconnect, multi_cloud, partner_connect
status String @default("active")
pricing Json @default("{}")
metadata Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
provider Provider @relation(fields: [providerId], references: [id])
orders ServiceOrder[]
@@map("product_offerings")
}
// ============================================================================
// ORDERS & SERVICES
// ============================================================================
model Quote {
id String @id @default(uuid())
tenantId String
productOfferingId String
sourceEndpointId String
targetEndpointId String
bandwidthMbps Int
monthlyRecurring Decimal @db.Decimal(10, 2)
setupFee Decimal @db.Decimal(10, 2)
currency String @default("USD")
validUntil DateTime
status String @default("draft") // draft, valid, expired, accepted
metadata Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("quotes")
}
model ServiceOrder {
id String @id @default(uuid())
tenantId String
userId String
productOfferingId String
providerId String
sourceEndpointId String
targetEndpointId String
bandwidthMbps Int
status String @default("draft")
externalReference String?
quoteId String?
metadata Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
user User @relation(fields: [userId], references: [id])
productOffering ProductOffering @relation(fields: [productOfferingId], references: [id])
provider Provider @relation(fields: [providerId], references: [id])
sourceEndpoint Endpoint @relation("SourceEndpoint", fields: [sourceEndpointId], references: [id])
targetEndpoint Endpoint @relation("TargetEndpoint", fields: [targetEndpointId], references: [id])
service Service?
provisioningTasks ProvisioningTask[]
@@map("service_orders")
}
model Service {
id String @id @default(uuid())
orderId String @unique
tenantId String
status String @default("provisioning")
activatedAt DateTime?
suspendedAt DateTime?
terminatedAt DateTime?
metadata Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
order ServiceOrder @relation(fields: [orderId], references: [id])
tenant Tenant @relation(fields: [tenantId], references: [id])
usageRecords UsageRecord[]
inventoryRecords InventoryRecord[]
@@map("services")
}
// ============================================================================
// PROVISIONING & ORCHESTRATION
// ============================================================================
model ProvisioningTask {
id String @id @default(uuid())
orderId String
taskType String // validate, provision, configure, activate
status String @default("pending")
adapterName String?
externalTaskId String?
retryCount Int @default(0)
maxRetries Int @default(3)
error String?
metadata Json @default("{}")
startedAt DateTime?
completedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
order ServiceOrder @relation(fields: [orderId], references: [id])
@@map("provisioning_tasks")
}
// ============================================================================
// INVENTORY & MONITORING
// ============================================================================
model InventoryRecord {
id String @id @default(uuid())
serviceId String
recordType String // connection, circuit, port
externalId String
providerData Json @default("{}")
status String
lastSyncedAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
@@map("inventory_records")
}
// ============================================================================
// BILLING
// ============================================================================
model UsageRecord {
id String @id @default(uuid())
serviceId String
recordType String // bandwidth, port_hour, data_transfer
quantity Decimal @db.Decimal(15, 4)
unit String
startTime DateTime
endTime DateTime
metadata Json @default("{}")
createdAt DateTime @default(now())
service Service @relation(fields: [serviceId], references: [id])
@@map("usage_records")
}
model Invoice {
id String @id @default(uuid())
tenantId String
invoiceNumber String @unique
status String @default("draft")
subtotal Decimal @db.Decimal(10, 2)
tax Decimal @db.Decimal(10, 2)
total Decimal @db.Decimal(10, 2)
currency String @default("USD")
periodStart DateTime
periodEnd DateTime
dueDate DateTime
paidAt DateTime?
metadata Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
@@map("invoices")
}
// ============================================================================
// API & WEBHOOKS
// ============================================================================
model ApiKey {
id String @id @default(uuid())
tenantId String
name String
keyHash String @unique
scopes Json @default("[]")
status String @default("active")
lastUsedAt DateTime?
expiresAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
@@map("api_keys")
}
model WebhookEndpoint {
id String @id @default(uuid())
tenantId String
url String
events Json @default("[]")
secret String
status String @default("active")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
deliveries WebhookDelivery[]
@@map("webhook_endpoints")
}
model WebhookDelivery {
id String @id @default(uuid())
endpointId String
eventType String
payload Json
status String @default("pending")
attempts Int @default(0)
lastAttempt DateTime?
response String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
endpoint WebhookEndpoint @relation(fields: [endpointId], references: [id])
@@map("webhook_deliveries")
}
// ============================================================================
// AUDIT & INCIDENTS
// ============================================================================
model AuditEvent {
id String @id @default(uuid())
aggregateType String
aggregateId String
eventType String
actorId String?
actorType String?
payload Json @default("{}")
ipAddress String?
userAgent String?
createdAt DateTime @default(now())
@@index([aggregateType, aggregateId])
@@index([eventType])
@@index([createdAt])
@@map("audit_events")
}
model Incident {
id String @id @default(uuid())
tenantId String?
serviceId String?
severity String // critical, high, medium, low
status String @default("open")
title String
description String
metadata Json @default("{}")
resolvedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("incidents")
}