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:
389
packages/database/prisma/schema.prisma
Normal file
389
packages/database/prisma/schema.prisma
Normal 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")
|
||||
}
|
||||
160
packages/database/prisma/seed.ts
Normal file
160
packages/database/prisma/seed.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { PrismaClient } from '@sdi/database';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('Seeding database...');
|
||||
|
||||
// Create providers
|
||||
const awsProvider = await prisma.provider.create({
|
||||
data: {
|
||||
name: 'Amazon Web Services',
|
||||
type: 'aws',
|
||||
status: 'active',
|
||||
metadata: {
|
||||
description: 'AWS Direct Connect',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const azureProvider = await prisma.provider.create({
|
||||
data: {
|
||||
name: 'Microsoft Azure',
|
||||
type: 'azure',
|
||||
status: 'active',
|
||||
metadata: {
|
||||
description: 'Azure ExpressRoute',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✓ Created providers');
|
||||
|
||||
// Create AWS endpoints
|
||||
const awsEndpoints = [
|
||||
{ region: 'us-east-1', metro: 'Washington DC', location: 'Equinix DC2' },
|
||||
{ region: 'us-west-2', metro: 'Seattle', location: 'Equinix SE2' },
|
||||
{ region: 'eu-west-1', metro: 'Dublin', location: 'Equinix DB3' },
|
||||
];
|
||||
|
||||
for (const ep of awsEndpoints) {
|
||||
await prisma.endpoint.create({
|
||||
data: {
|
||||
providerId: awsProvider.id,
|
||||
kind: 'cloud_region',
|
||||
region: ep.region,
|
||||
metro: ep.metro,
|
||||
location: ep.location,
|
||||
status: 'available',
|
||||
metadata: { provider: 'aws' },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✓ Created AWS endpoints');
|
||||
|
||||
// Create Azure endpoints
|
||||
const azureEndpoints = [
|
||||
{ region: 'eastus', metro: 'Washington DC', location: 'Equinix DC2' },
|
||||
{ region: 'westus2', metro: 'Seattle', location: 'Equinix SE2' },
|
||||
{ region: 'westeurope', metro: 'Amsterdam', location: 'Equinix AM3' },
|
||||
];
|
||||
|
||||
for (const ep of azureEndpoints) {
|
||||
await prisma.endpoint.create({
|
||||
data: {
|
||||
providerId: azureProvider.id,
|
||||
kind: 'cloud_region',
|
||||
region: ep.region,
|
||||
metro: ep.metro,
|
||||
location: ep.location,
|
||||
status: 'available',
|
||||
metadata: { provider: 'azure' },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✓ Created Azure endpoints');
|
||||
|
||||
// Create product offerings
|
||||
await prisma.productOffering.create({
|
||||
data: {
|
||||
providerId: awsProvider.id,
|
||||
name: 'AWS Direct Connect - 1Gbps',
|
||||
description: 'Dedicated 1Gbps connection to AWS',
|
||||
type: 'cloud_interconnect',
|
||||
status: 'active',
|
||||
pricing: {
|
||||
setupFee: 500,
|
||||
monthlyRecurring: 50,
|
||||
currency: 'USD',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.productOffering.create({
|
||||
data: {
|
||||
providerId: azureProvider.id,
|
||||
name: 'Azure ExpressRoute - 1Gbps',
|
||||
description: 'Dedicated 1Gbps connection to Azure',
|
||||
type: 'cloud_interconnect',
|
||||
status: 'active',
|
||||
pricing: {
|
||||
setupFee: 600,
|
||||
monthlyRecurring: 60,
|
||||
currency: 'USD',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✓ Created product offerings');
|
||||
|
||||
// Create demo tenant
|
||||
const tenant = await prisma.tenant.create({
|
||||
data: {
|
||||
name: 'Demo Corporation',
|
||||
slug: 'demo-corp',
|
||||
status: 'active',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✓ Created demo tenant');
|
||||
|
||||
// Create demo user
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
tenantId: tenant.id,
|
||||
email: 'admin@demo-corp.com',
|
||||
name: 'Demo Admin',
|
||||
status: 'active',
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.role.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
name: 'admin',
|
||||
permissions: {
|
||||
orders: ['create', 'read', 'update', 'delete'],
|
||||
services: ['read', 'modify', 'suspend', 'terminate'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✓ Created demo user with admin role');
|
||||
|
||||
console.log('\n✅ Database seeded successfully!');
|
||||
console.log('\nDemo credentials:');
|
||||
console.log(` Tenant: ${tenant.slug}`);
|
||||
console.log(` Email: ${user.email}`);
|
||||
console.log(` Tenant ID: ${tenant.id}`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error('Error seeding database:', e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
Reference in New Issue
Block a user