// Redirect Intelligence v2 - Database Schema // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" output = "../../../node_modules/.prisma/client" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(cuid()) email String @unique name String passwordHash String @map("password_hash") createdAt DateTime @default(now()) @map("created_at") lastLoginAt DateTime? @map("last_login_at") memberships OrgMembership[] auditLogs AuditLog[] bulkJobs BulkJob[] @@map("users") } model Organization { id String @id @default(cuid()) name String plan String @default("free") createdAt DateTime @default(now()) @map("created_at") memberships OrgMembership[] projects Project[] apiKeys ApiKey[] auditLogs AuditLog[] bulkJobs BulkJob[] @@map("organizations") } model OrgMembership { id String @id @default(cuid()) orgId String @map("org_id") userId String @map("user_id") role Role organization Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([orgId, userId]) @@map("org_memberships") } model Project { id String @id @default(cuid()) orgId String @map("org_id") name String settingsJson Json @map("settings_json") @default("{}") createdAt DateTime @default(now()) @map("created_at") organization Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) checks Check[] bulkJobs BulkJob[] @@map("projects") } model Check { id String @id @default(cuid()) projectId String @map("project_id") inputUrl String @map("input_url") method String @default("GET") headersJson Json @map("headers_json") @default("{}") userAgent String? @map("user_agent") startedAt DateTime @map("started_at") finishedAt DateTime? @map("finished_at") status CheckStatus finalUrl String? @map("final_url") totalTimeMs Int? @map("total_time_ms") reportId String? @map("report_id") project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) hops Hop[] sslInspections SslInspection[] seoFlags SeoFlags? securityFlags SecurityFlags? reports Report[] @@index([projectId, startedAt(sort: Desc)]) @@map("checks") } model Hop { id String @id @default(cuid()) checkId String @map("check_id") hopIndex Int @map("hop_index") url String scheme String? statusCode Int? @map("status_code") redirectType RedirectType @map("redirect_type") latencyMs Int? @map("latency_ms") contentType String? @map("content_type") reason String? responseHeadersJson Json @map("response_headers_json") @default("{}") check Check @relation(fields: [checkId], references: [id], onDelete: Cascade) @@index([checkId, hopIndex]) @@map("hops") } model SslInspection { id String @id @default(cuid()) checkId String @map("check_id") host String validFrom DateTime? @map("valid_from") validTo DateTime? @map("valid_to") daysToExpiry Int? @map("days_to_expiry") issuer String? protocol String? warningsJson Json @map("warnings_json") @default("[]") check Check @relation(fields: [checkId], references: [id], onDelete: Cascade) @@map("ssl_inspections") } model SeoFlags { id String @id @default(cuid()) checkId String @unique @map("check_id") robotsTxtStatus String? @map("robots_txt_status") robotsTxtRulesJson Json @map("robots_txt_rules_json") @default("{}") metaRobots String? @map("meta_robots") canonicalUrl String? @map("canonical_url") sitemapPresent Boolean @default(false) @map("sitemap_present") noindex Boolean @default(false) nofollow Boolean @default(false) check Check @relation(fields: [checkId], references: [id], onDelete: Cascade) @@map("seo_flags") } model SecurityFlags { id String @id @default(cuid()) checkId String @unique @map("check_id") safeBrowsingStatus String? @map("safe_browsing_status") mixedContent MixedContent @map("mixed_content") @default(NONE) httpsToHttp Boolean @map("https_to_http") @default(false) check Check @relation(fields: [checkId], references: [id], onDelete: Cascade) @@map("security_flags") } model Report { id String @id @default(cuid()) checkId String @map("check_id") markdownPath String? @map("markdown_path") pdfPath String? @map("pdf_path") createdAt DateTime @default(now()) @map("created_at") check Check @relation(fields: [checkId], references: [id], onDelete: Cascade) @@map("reports") } model BulkJob { id String @id @default(cuid()) userId String @map("user_id") organizationId String? @map("organization_id") projectId String? @map("project_id") uploadPath String @map("upload_path") status JobStatus @default(PENDING) totalUrls Int @default(0) @map("total_urls") processedUrls Int @default(0) @map("processed_urls") successfulUrls Int @default(0) @map("successful_urls") failedUrls Int @default(0) @map("failed_urls") configJson Json @default("{}") @map("config_json") urlsJson Json? @map("urls_json") resultsJson Json? @map("results_json") progressJson Json @default("{}") @map("progress_json") createdAt DateTime @default(now()) @map("created_at") startedAt DateTime? @map("started_at") finishedAt DateTime? @map("finished_at") completedAt DateTime? @map("completed_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade) project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull) @@map("bulk_jobs") } model ApiKey { id String @id @default(cuid()) orgId String @map("org_id") name String tokenHash String @unique @map("token_hash") permsJson Json @map("perms_json") @default("{}") rateLimitQuota Int @map("rate_limit_quota") @default(1000) createdAt DateTime @default(now()) @map("created_at") organization Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) @@index([tokenHash]) @@map("api_keys") } model AuditLog { id String @id @default(cuid()) orgId String @map("org_id") actorUserId String? @map("actor_user_id") action String entity String entityId String @map("entity_id") metaJson Json @map("meta_json") @default("{}") createdAt DateTime @default(now()) @map("created_at") organization Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) actor User? @relation(fields: [actorUserId], references: [id], onDelete: SetNull) @@map("audit_logs") } enum Role { OWNER ADMIN MEMBER } enum CheckStatus { OK ERROR TIMEOUT LOOP } enum RedirectType { HTTP_301 HTTP_302 HTTP_307 HTTP_308 META_REFRESH JS FINAL OTHER } enum MixedContent { NONE PRESENT FINAL_TO_HTTP } enum JobStatus { PENDING QUEUED RUNNING COMPLETED FAILED CANCELLED ERROR }