| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- /// One installed connector for one organisation: the account it is linked
- /// to, its sealed credentials, and the settings the workshop chose. Rows in
- /// the other integration tables hang off this one.
- model IntegrationConnection {
- id String @id @default(cuid())
- organizationId String
- organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
- /// Connector id from the registry, for example 'google-calendar'.
- connectorId String
- /// pending | active | error | disconnected
- status String @default("pending")
- label String?
- /// Tokens or keys as one sealed string; only the vault reads it.
- credentials String?
- settings Json @default("{}")
- /// Connector-owned state such as sync cursors and last-run times.
- state Json @default("{}")
- externalAccountId String?
- externalAccountName String?
- scopes String?
- /// OAuth handshake nonce, set while the connection is pending.
- oauthState String? @unique
- lastHealthAt DateTime?
- lastSyncAt DateTime?
- lastError String?
- createdById String
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- links IntegrationLink[]
- jobs IntegrationJob[]
- logs IntegrationLog[]
- externalEvents ExternalCalendarEvent[]
- backupRuns BackupRun[]
- repairData RepairDataCache[]
- @@unique([organizationId, connectorId])
- @@index([organizationId])
- @@map("integration_connections")
- }
- /// The remote id a local record has in one connected system, which is what
- /// makes every push an update instead of a duplicate.
- model IntegrationLink {
- id String @id @default(cuid())
- connectionId String
- connection IntegrationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
- /// Local model name, for example 'ServiceRecord'.
- entityType String
- entityId String
- remoteId String
- remoteUrl String?
- /// Connector-owned extras, such as a meeting link.
- metadata Json?
- checksum String?
- remoteUpdatedAt DateTime?
- lastSyncedAt DateTime @default(now())
- createdAt DateTime @default(now())
- @@unique([connectionId, entityType, entityId])
- @@index([connectionId, remoteId])
- @@index([entityType, entityId])
- @@map("integration_links")
- }
- /// A unit of work for a connector, claimed and retried by the cron.
- model IntegrationJob {
- id String @id @default(cuid())
- connectionId String
- connection IntegrationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
- organizationId String
- kind String
- payload Json?
- /// Set while the job is pending so the same work is queued once; cleared when it runs.
- idempotencyKey String?
- /// queued | running | done | failed | dead
- status String @default("queued")
- attempts Int @default(0)
- maxAttempts Int @default(5)
- runAfter DateTime @default(now())
- lockedAt DateTime?
- startedAt DateTime?
- finishedAt DateTime?
- error String?
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- @@unique([connectionId, idempotencyKey])
- @@index([status, runAfter])
- @@index([connectionId, createdAt])
- @@index([organizationId, createdAt])
- @@map("integration_jobs")
- }
- /// What a connector did and why it failed, per connection, kept for 30 days.
- model IntegrationLog {
- id String @id @default(cuid())
- connectionId String
- connection IntegrationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
- jobId String?
- /// info | warn | error
- level String @default("info")
- message String
- details Json?
- createdAt DateTime @default(now())
- @@index([connectionId, createdAt])
- @@map("integration_logs")
- }
- /// Busy time pulled from a connected calendar, shown read-only on the
- /// workshop calendar. A cache: rebuilt by the pull job, never edited.
- model ExternalCalendarEvent {
- id String @id @default(cuid())
- connectionId String
- connection IntegrationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
- organizationId String
- organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
- remoteId String
- calendarId String
- title String
- startAt DateTime
- endAt DateTime
- allDay Boolean @default(false)
- /// The event's page in the vendor's own calendar, when the vendor gives one.
- remoteUrl String?
- remoteUpdatedAt DateTime?
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- @@unique([connectionId, remoteId])
- @@index([organizationId, startAt])
- @@map("external_calendar_events")
- }
- /// What a safety authority knows about one model year: recalls, owner
- /// complaints and crash ratings, as the connector shaped them. Public data,
- /// keyed by model rather than by workshop or vehicle, so a 2003 Accord is
- /// fetched once for everyone. A cache: rebuilt by the connector, never edited.
- model VehicleSafetyReport {
- id String @id @default(cuid())
- /// Connector id, such as "nhtsa".
- source String
- /// Make, model and year as the workshop wrote them, upper-cased and trimmed.
- make String
- model String
- year Int
- /// False when the authority answered but had no such model.
- found Boolean @default(true)
- data Json
- /// Counts lifted out of data so a list can show a badge without parsing it.
- recallCount Int @default(0)
- complaintCount Int @default(0)
- fetchedAt DateTime @default(now())
- createdAt DateTime @default(now())
- @@unique([source, make, model, year])
- @@index([fetchedAt])
- @@map("vehicle_safety_reports")
- }
- /// Labor times or fluid specs a repair data source answered for one model
- /// year, kept for as long as the key that fetched them is on a plan whose
- /// terms allow it (Open Labor Project's paid plans), so a vehicle looked up
- /// once is not paid for again. Deleted when the key's plan drops to free, the
- /// key is refused, or the workshop disconnects.
- model RepairDataCache {
- id String @id @default(cuid())
- /// Connector id, such as "openlaborproject".
- source String
- /// Whose key fetched it: the connection id for a workshop's own key, or
- /// "platform" for the key the server holds, whose rows serve every
- /// workshop that uses it.
- owner String
- /// Set for a workshop's own key, so its rows go when it disconnects.
- connectionId String?
- connection IntegrationConnection? @relation(fields: [connectionId], references: [id], onDelete: Cascade)
- /// labor | fluids
- kind String
- /// "make|model|year" as the workshop wrote them, lower-cased, or
- /// "vin:<sha-256>" when only the VIN found the vehicle. No VIN is stored.
- lookupKey String
- /// Bumped when the stored shape changes; an older row is fetched again.
- version Int
- data Json
- fetchedAt DateTime @default(now())
- createdAt DateTime @default(now())
- @@unique([source, owner, kind, lookupKey])
- @@index([connectionId])
- @@map("repair_data_cache")
- }
- /// One backup of one workshop to one destination: a scheduled or manual
- /// restic run, what it produced and how it ended. Kept so the connection page
- /// can show the last good backup and the cron can tell when one is overdue.
- model BackupRun {
- id String @id @default(cuid())
- organizationId String
- organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
- /// destination: the workshop's own bucket or folder (connectionId set).
- /// platform: the repository Torqvoice keeps for the workshop on the cloud
- /// instance (no connection).
- kind String @default("destination")
- connectionId String?
- connection IntegrationConnection? @relation(fields: [connectionId], references: [id], onDelete: Cascade)
- /// scheduled | manual | restore
- trigger String
- /// queued | running | done | failed | skipped
- status String @default("queued")
- startedAt DateTime?
- finishedAt DateTime?
- /// restic snapshot id: the one a backup made, or the one a restore put back.
- snapshotId String?
- /// The snapshot of the workshop as it was taken right before a restore
- /// replaced it, so a restore can itself be undone.
- safetySnapshotId String?
- /// Who asked: a member's user id, or null for a run the schedule or
- /// support (through torqvoice.com) started.
- requestedBy String?
- /// Why support restored, as typed on torqvoice.com; shown to the workshop.
- reason String?
- filesNew Int?
- bytesAdded BigInt?
- totalBytes BigInt?
- error String?
- createdAt DateTime @default(now())
- @@index([connectionId, createdAt])
- @@index([organizationId, kind, createdAt])
- @@index([status, createdAt])
- @@map("backup_runs")
- }
- /// The backups Torqvoice runs for a workshop on the cloud instance: one
- /// restic repository per workshop in a bucket of ours, hourly, Pro and up.
- /// One row per workshop that has ever been in the cycle; the run rows carry
- /// the history, this row carries where the schedule stands.
- model PlatformBackup {
- organizationId String @id
- organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
- lastScheduledAt DateTime?
- lastGoodAt DateTime?
- lastPruneAt DateTime?
- lastCheckAt DateTime?
- /// One retry half an hour after a failed or skipped run.
- retryAt DateTime?
- /// Set while the workshop's plan no longer includes platform backups.
- /// Runs stop; the repository is kept for a grace period, then forgotten.
- pausedAt DateTime?
- /// The repository's snapshots were forgotten after the grace period.
- purgedAt DateTime?
- repositorySize BigInt?
- /// The restore points in the repository after the last run, newest first:
- /// [{ id, shortId, time, bytes, trigger, preRestore }]. What the Backups
- /// card and torqvoice.com list, so neither has to open the repository.
- snapshots Json @default("[]")
- lastFailureMailAt DateTime?
- lastOverdueAlertAt DateTime?
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- @@index([pausedAt])
- @@map("platform_backups")
- }
|