auth.prisma 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. model User {
  2. id String @id @default(cuid())
  3. name String
  4. email String @unique
  5. emailVerified Boolean @default(false)
  6. /// The mobile a technician signs in with, and the number a workshop holds
  7. /// for whoever this is.
  8. ///
  9. /// Deliberately not unique. When a desk creates an account for a mechanic
  10. /// standing at the counter, a second workshop may create another for the
  11. /// same human, and neither should be able to block the other. Every lookup
  12. /// reaches it through a technician row and so is already scoped to one
  13. /// workshop; the number alone is never the question being asked.
  14. phone String?
  15. image String?
  16. isSuperAdmin Boolean @default(false)
  17. createdAt DateTime @default(now())
  18. updatedAt DateTime @updatedAt
  19. twoFactorEnabled Boolean @default(false)
  20. termsAcceptedAt DateTime?
  21. lastLogin DateTime?
  22. lastSeen DateTime?
  23. /// App version this user last saw/dismissed the update banner for.
  24. /// Null = never seeded (seeded silently on first load, no banner).
  25. lastSeenVersion String?
  26. /// The release the update banner is up for, and when it first appeared to
  27. /// this user on any device. The hour it stays up runs from here, so a
  28. /// second device does not restart them and a closed tab cannot stop them.
  29. updateBannerVersion String?
  30. updateBannerShownAt DateTime?
  31. /// When this user last opened the "What's new" panel. Entries published
  32. /// after it (and after the account was created) carry the unread dot.
  33. whatsNewSeenAt DateTime?
  34. /// Per-user dashboard grid layout (card positions/sizes/hidden set).
  35. /// Null = default layout. Shape validated in dashboardLayoutActions.
  36. dashboardLayout Json?
  37. sessions Session[]
  38. devices UserDevice[]
  39. accounts Account[]
  40. pushDevices PushDevice[]
  41. appSetupCodes TechnicianSetupCode[]
  42. vehicles Vehicle[]
  43. customers Customer[]
  44. settings AppSetting[]
  45. inventoryParts InventoryPart[]
  46. quotes Quote[]
  47. customFieldDefinitions CustomFieldDefinition[]
  48. twoFactor TwoFactor?
  49. passkeys Passkey[]
  50. organizationMembers OrganizationMember[]
  51. dashboardWidgets DashboardWidget[]
  52. auditLogs AuditLog[]
  53. openedServiceRecords ServiceRecord[] @relation("ServiceRecordCreatedBy")
  54. confirmedConcerns ServiceConcern[] @relation("ConcernConfirmedBy")
  55. appointmentsCreated Appointment[] @relation("AppointmentCreatedBy")
  56. appointmentNotifications AppointmentNotification[] @relation("AppointmentNotificationUser")
  57. recordedConditionMarks ConditionMark[] @relation("ConditionMarkRecordedBy")
  58. resolvedConditionMarks ConditionMark[] @relation("ConditionMarkResolvedBy")
  59. technicians Technician[]
  60. stockMovements StockMovement[]
  61. tireWarehouses TireWarehouse[]
  62. tireSets TireSet[]
  63. tireSetAttachments TireSetAttachment[]
  64. tireMeasurements TireMeasurement[]
  65. tireMovements TireMovement[]
  66. tireTreatments TireTreatment[]
  67. importBatches ImportBatch[]
  68. @@map("users")
  69. }
  70. model Session {
  71. id String @id @default(cuid())
  72. expiresAt DateTime
  73. token String @unique
  74. createdAt DateTime @default(now())
  75. updatedAt DateTime @updatedAt
  76. ipAddress String?
  77. userAgent String?
  78. userId String
  79. user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  80. /// The one workshop this session may act in, for sessions minted without a
  81. /// password: an app setup code from the desk, or a one-time code sent to a
  82. /// technician's phone. Null for every ordinary sign-in.
  83. ///
  84. /// Those sessions are handed out on a workshop's say-so, and a workshop can
  85. /// only vouch for itself. Left unscoped, the token was the person's whole
  86. /// account: whoever redeemed a setup code could name any other workshop the
  87. /// technician belongs to in a header and act there with their role. When
  88. /// this is set it is the workshop, and no header or cookie can choose
  89. /// another. Removing the workshop ends the session with it.
  90. organizationId String?
  91. organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  92. @@index([userId])
  93. @@index([organizationId])
  94. @@map("sessions")
  95. }
  96. model Account {
  97. id String @id @default(cuid())
  98. accountId String
  99. providerId String
  100. userId String
  101. user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  102. accessToken String?
  103. refreshToken String?
  104. idToken String?
  105. accessTokenExpiresAt DateTime?
  106. refreshTokenExpiresAt DateTime?
  107. scope String?
  108. password String?
  109. createdAt DateTime @default(now())
  110. updatedAt DateTime @updatedAt
  111. @@index([userId])
  112. @@map("accounts")
  113. }
  114. model Verification {
  115. id String @id @default(cuid())
  116. identifier String @unique
  117. value String
  118. expiresAt DateTime
  119. createdAt DateTime? @default(now())
  120. updatedAt DateTime? @updatedAt
  121. @@map("verifications")
  122. }
  123. model TwoFactor {
  124. id String @id @default(cuid())
  125. secret String
  126. backupCodes String
  127. userId String @unique
  128. user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  129. // better-auth's two-factor plugin writes these three since 1.6: a secret
  130. // stays unverified until the first code is entered, and repeated wrong
  131. // codes lock the method for a while. Rows from before default to verified,
  132. // because those people already proved their app worked.
  133. verified Boolean @default(true)
  134. failedVerificationCount Int @default(0)
  135. lockedUntil DateTime?
  136. @@map("two_factor")
  137. }
  138. model Passkey {
  139. id String @id @default(cuid())
  140. name String?
  141. publicKey String
  142. userId String
  143. user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  144. credentialID String @unique
  145. counter Int
  146. deviceType String
  147. backedUp Boolean
  148. transports String?
  149. aaguid String?
  150. createdAt DateTime @default(now())
  151. @@index([userId])
  152. @@map("passkeys")
  153. }
  154. /// A browser or phone this person has signed in from before.
  155. ///
  156. /// Sessions come and go (sign-out, expiry, a password change that ends the
  157. /// others), so the session table cannot say whether a device is new; this can.
  158. /// A browser is known by a random id in a long-lived cookie, set the first
  159. /// time it signs in; a client without cookies, such as the technician app,
  160. /// is known by its user agent. The first device an account ever sees is
  161. /// recorded quietly; every later one earns a "new sign-in" mail.
  162. model UserDevice {
  163. id String @id @default(cuid())
  164. /// Cookie id, or a hash of the user agent when no cookie can be kept.
  165. deviceKey String
  166. userAgent String?
  167. lastIp String?
  168. firstSeenAt DateTime @default(now())
  169. lastSeenAt DateTime @default(now())
  170. userId String
  171. user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  172. @@unique([userId, deviceKey])
  173. @@index([userId])
  174. @@map("user_devices")
  175. }