This repository has no description
1generator goprisma {
2 provider = "go run github.com/steebchen/prisma-client-go"
3 previewFeatures = ["fullTextSearchPostgres", "postgresqlExtensions"]
4}
5
6// See https://pothos-graphql.dev/docs/plugins/prisma#setup
7
8datasource db {
9 provider = "postgresql"
10 url = env("DATABASE_URL")
11 extensions = [fuzzystrmatch, pgcrypto, unaccent, pg_trgm]
12}
13
14/// Users are the people who use the app
15model User {
16 id String @id @default(dbgenerated("nanoid('u:'::text)"))
17 uid String @unique @db.VarChar(255)
18 createdAt DateTime @default(now())
19 lastSeenAt DateTime @default(now())
20
21 // School details
22 schoolServer String? @db.VarChar(255)
23 schoolUid String? @db.VarChar(255)
24 // email of the user from the school's LDAP
25 schoolEmail String? @db.VarChar(255)
26
27 email String @unique @db.VarChar(255)
28 otherEmails String[] @db.VarChar(255)
29 firstName String @db.VarChar(255)
30 lastName String @db.VarChar(255)
31 majorId String?
32 major Major? @relation(fields: [majorId], references: [id], onUpdate: Cascade, onDelete: Restrict)
33 minor Minor? @relation(fields: [minorId], references: [id])
34 minorId String?
35 graduationYear Int
36 apprentice Boolean @default(false)
37 address String @default("") @db.VarChar(255)
38 birthday DateTime?
39 description String @default("") @db.Text
40 nickname String @default("") @db.VarChar(255)
41 pronouns String @default("") @db.VarChar(255)
42 phone String @default("") @db.VarChar(255)
43 pictureFile String @default("") @db.VarChar(255)
44 links Link[]
45 godparentId String?
46 cededImageRightsToTVn7 Boolean @default(false)
47 enabledNotificationChannels NotificationChannel[] @default([Articles, Shotguns, Permissions, GroupBoard, GodparentRequests, Comments, Other])
48 latestVersionSeenInChangelog String @default("0.0.0")
49 bot Boolean @default(false)
50 // Not shown on profile, used to autofill Lydia payment request forms
51 lydiaPhone String @default("") @db.VarChar(255)
52 // Prevent these themes from being auto-deployed to the user
53 blockedThemes Theme[]
54
55 // Permissions
56 admin Boolean @default(false)
57 canEditGroups StudentAssociation[] @relation("studentAssociationGroupsEditor")
58 canAccessDocuments Boolean @default(false)
59
60 // Relationships
61 articles Article[]
62 groups GroupMember[]
63 credentials Credential[]
64 Reservation Registration[] @relation("author")
65 managedEvents EventManager[]
66 logs LogEntry[]
67 events Event[]
68 notificationSubscriptions NotificationSubscription[]
69 notifications Notification[]
70 godparent User? @relation("mentorship", fields: [godparentId], references: [id], onDelete: SetNull) // Le parrain ou la marraine
71 godchildren User[] @relation("mentorship") // Les filleul(e)s
72 incomingGodparentRequests GodparentRequest[] @relation("godparent")
73 outgoingGodparentRequests GodparentRequest[] @relation("godchild")
74 passwordResets PasswordReset[]
75 emailChanges EmailChange[]
76 Announcement Announcement[]
77 contributions Contribution[]
78 verifications Registration[] @relation("verifiedBy")
79 oppositions Registration[] @relation("opposedBy")
80 cancellations Registration[] @relation("cancelledBy")
81 receivedBookings Registration[] @relation("beneficiaryOf")
82 documents Document[]
83 bannedFromEvents Event[] @relation("bannedFromEvents")
84 reactions Reaction[]
85 formAnswers Answer[]
86 createdForms Form[]
87 completedForms Form[] @relation("completedForms")
88 partiallyCompletedForms Form[] @relation("partiallyCompletedForms")
89 adminOfStudentAssociations StudentAssociation[] @relation("studentAssociationAdmins")
90 bookmarks Bookmark[]
91 sharedPosts Article[] @relation("articleShares")
92 sharedEvents Event[] @relation("eventShares")
93 seenBookings Registration[] @relation("seenBy")
94 eventManagerInvites EventManagerInvite[] @relation("eventManagerInviteUses")
95 invitedToTickets Ticket[] @relation("invitedTo")
96
97 // For full-text search
98 search Unsupported("tsvector") @default(dbgenerated("''::tsvector"))
99 claimedPromotions PromotionCode[]
100 formsWithMarkedCheckboxes Form[] @relation("formsWithMarkedCheckbox")
101 createdPages Page[]
102 pointOfContactFor Registration[] @relation("pointOfContactFor")
103
104 @@unique([schoolServer, schoolUid])
105 @@index([search], type: Gin)
106}
107
108/// A bookmarked page, used to make personal quick access links
109model Bookmark {
110 id String @id @default(dbgenerated("nanoid('bookmark:'::text)"))
111 createdAt DateTime @default(now())
112 updatedAt DateTime @updatedAt
113 user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
114 userId String
115 // The path to the bookmarked page. Up to the frontend's interpretation
116 path String
117
118 @@unique([userId, path])
119}
120
121/// Requests to become someone's godchild. Gets deleted once the request has been accepted (or denied). godchild is the requester, godparent is the requested.
122model GodparentRequest {
123 id String @id @default(dbgenerated("nanoid('godparentreq:'::text)"))
124 createdAt DateTime @default(now())
125 updatedAt DateTime @updatedAt
126
127 godchild User @relation("godchild", fields: [godchildId], references: [id], onDelete: Cascade)
128 godchildId String
129 godparent User @relation("godparent", fields: [godparentId], references: [id], onDelete: Cascade)
130 godparentId String
131
132 @@unique([godchildId, godparentId])
133}
134
135/// UserCandidates are users in the registration process
136model UserCandidate {
137 id String @id @default(dbgenerated("nanoid('candidate:'::text)"))
138 token String @unique
139 createdAt DateTime @default(now())
140
141 email String @unique @db.VarChar(255)
142 emailValidated Boolean @default(false)
143 uid String @default("") @db.VarChar(255)
144 firstName String @default("") @db.VarChar(255)
145 lastName String @default("") @db.VarChar(255)
146
147 churrosPassword String @db.VarChar(255)
148 ldapPassword String @db.VarChar(255)
149
150 majorId String?
151 major Major? @relation(fields: [majorId], references: [id], onUpdate: Cascade, onDelete: Restrict)
152 graduationYear Int?
153 apprentice Boolean @default(false)
154
155 birthday DateTime?
156 cededImageRightsToTVn7 Boolean @default(false)
157
158 usingQuickSignup QuickSignup? @relation(fields: [quickSignupId], references: [id])
159 quickSignupId String?
160}
161
162/// A password reset token
163model PasswordReset {
164 id String @id @default(dbgenerated("nanoid('passreset:'::text)"))
165 createdAt DateTime @default(now())
166 updatedAt DateTime @updatedAt
167 user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
168 userId String
169 expiresAt DateTime?
170}
171
172/// A email validation request
173model EmailChange {
174 id String @id @default(dbgenerated("nanoid('emailchange:'::text)"))
175 createdAt DateTime @default(now())
176 updatedAt DateTime @updatedAt
177 email String
178 expiresAt DateTime?
179 user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
180 userId String
181 pending Boolean @default(true)
182 // token is not exposed to the API, whereas id has to be
183 token String @unique @default(dbgenerated("nanoid(''::text, 30)"))
184}
185
186model QuickSignup {
187 id String @id @default(dbgenerated("nanoid('quicksignup:'::text, 6)"))
188 createdAt DateTime @default(now())
189 updatedAt DateTime @updatedAt
190 validUntil DateTime
191 school School @relation(fields: [schoolId], references: [id], onDelete: Cascade, onUpdate: Cascade)
192 schoolId String
193 candidates UserCandidate[]
194}
195
196// Enum for the different kinds of logos
197enum LogoSourceType {
198 InternalLink
199 ExternalLink
200 GroupLogo
201 Icon
202}
203
204/// A service
205model Service {
206 id String @id @default(dbgenerated("nanoid('service:'::text)"))
207 name String @db.VarChar(255)
208 url String @default("") @db.VarChar(255)
209 description String @default("") @db.VarChar(255)
210 logo String @db.VarChar(255)
211 logoSourceType LogoSourceType
212 schoolId String?
213 school School? @relation(fields: [schoolId], references: [id], onUpdate: Cascade, onDelete: SetNull)
214 studentAssociationId String?
215 studentAssociation StudentAssociation? @relation(fields: [studentAssociationId], references: [id], onUpdate: Cascade, onDelete: SetNull)
216 groupId String?
217 group Group? @relation(fields: [groupId], references: [id], onUpdate: Cascade, onDelete: SetNull)
218 importance Int @default(0)
219 hidden Boolean @default(false)
220
221 @@unique([schoolId, studentAssociationId, groupId, url])
222 @@unique([schoolId, studentAssociationId, groupId, name])
223}
224
225/// A single external link
226model Link {
227 id String @id @default(dbgenerated("nanoid('link:'::text)"))
228 name String @db.VarChar(255)
229 value String @db.VarChar(255)
230 createdAt DateTime @default(now())
231
232 // All resources that can have links
233 User User? @relation(fields: [userId], references: [id], onDelete: SetNull)
234 userId String?
235 StudentAssociation StudentAssociation? @relation(fields: [studentAssociationId], references: [id])
236 studentAssociationId String?
237 Group Group? @relation(fields: [groupId], references: [id])
238 groupId String?
239 Article Article? @relation(fields: [articleId], references: [id])
240 articleId String?
241 Event Event? @relation(fields: [eventId], references: [id])
242 eventId String?
243 Ticket Ticket? @relation(fields: [ticketId], references: [id])
244 ticketId String?
245 Notification Notification? @relation(fields: [notificationId], references: [id])
246 notificationId String?
247 Subject Subject? @relation(fields: [subjectId], references: [id])
248 subjectId String?
249
250 @@unique([name, userId, studentAssociationId, groupId, articleId, eventId, ticketId, notificationId, subjectId])
251 @@unique([userId, value])
252 @@unique([studentAssociationId, value])
253 @@unique([groupId, value])
254 @@unique([articleId, value])
255 @@unique([eventId, value])
256 @@unique([ticketId, value])
257 @@unique([notificationId, value])
258 @@unique([subjectId, value])
259}
260
261/// A school syllabus
262model Major {
263 id String @id @default(dbgenerated("nanoid('major:'::text)"))
264 uid String @unique @db.VarChar(255)
265 name String @db.VarChar(255)
266 shortName String @default("") @db.VarChar(255)
267 ldapSchoolUid String? @db.VarChar(255)
268 pictureFile String @default("") @db.VarChar(255)
269 createdAt DateTime @default(now())
270 updatedAt DateTime @default(now()) @updatedAt
271 discontinued Boolean @default(false)
272
273 schools School[] @relation("MajorToSchool")
274 ldapSchool School? @relation("ldapSchool", fields: [ldapSchoolUid], references: [uid], onUpdate: Cascade, onDelete: SetNull)
275 students User[]
276 userCandidates UserCandidate[]
277 accessibleTickets Ticket[]
278 subjects Subject[]
279 minors Minor[]
280}
281
282model Minor {
283 id String @id @default(dbgenerated("nanoid('minor:'::text)"))
284 name String
285 shortName String @default("")
286 slug String
287 majors Major[]
288 yearTier Int
289 subjects Subject[]
290 users User[]
291
292 @@unique([slug, yearTier])
293}
294
295model School {
296 id String @id @default(dbgenerated("nanoid('school:'::text)"))
297 uid String @unique @db.VarChar(255)
298 name String @db.VarChar(255)
299 color String @db.VarChar(7)
300 studentMailDomain String @default("") @db.VarChar(255)
301 aliasMailDomains String[] @default([])
302 description String @default("") @db.Text
303 address String @default("") @db.VarChar(255)
304 pictureFile String @default("") @db.VarChar(255)
305
306 majors Major[] @relation("MajorToSchool")
307 majorsLdapSchool Major[] @relation("ldapSchool")
308 studentAssociations StudentAssociation[]
309 accessibleTickets Ticket[]
310 contributionOptions ContributionOption[]
311 services Service[]
312 quickSignups QuickSignup[]
313}
314
315enum CredentialType {
316 Password
317 Token
318 GroupAccessToken
319 Google // External token, used for google sheets integration (and possibly more in the future)
320}
321
322/// A credential is a way to authenticate a user
323model Credential {
324 id String @id @default(dbgenerated("nanoid('credential:'::text)"))
325 userId String?
326 groupId String?
327 name String @default("") @db.VarChar(255)
328 type CredentialType
329 value String @db.VarChar(255)
330 userAgent String @default("") @db.VarChar(255)
331 createdAt DateTime @default(now())
332 expiresAt DateTime?
333 refresh String? @db.VarChar(255)
334
335 user User? @relation(fields: [userId], references: [id], onUpdate: Cascade, onDelete: Cascade)
336 group Group? @relation(fields: [groupId], references: [id], onUpdate: Cascade, onDelete: Cascade)
337}
338
339/// There is one student association per school
340model StudentAssociation {
341 id String @id @default(dbgenerated("nanoid('ae:'::text)"))
342 uid String @unique @db.VarChar(255)
343 description String @default("") @db.Text
344 schoolId String
345 name String @unique @db.VarChar(255)
346 links Link[]
347 pictureFile String @default("") @db.VarChar(255)
348 heroBackgroundFile String @default("") @db.VarChar(255) /// Used for the student association's homepage
349 allPrezMailingList String @default("") @db.VarChar(255)
350 allTrezMailingList String @default("") @db.VarChar(255)
351 allBoardMailingList String @default("") @db.VarChar(255)
352 internalMailDomain String @default("") @db.VarChar(255)
353
354 createdAt DateTime @default(now())
355 updatedAt DateTime @updatedAt
356
357 school School @relation(fields: [schoolId], references: [id], onUpdate: Cascade, onDelete: Restrict)
358 groups Group[]
359 board Group? @relation("studentAssociationBoard", fields: [boardId], references: [id], onDelete: SetNull, onUpdate: Cascade)
360 boardId String? @unique
361 lydiaAccounts LydiaAccount[]
362 contributionOptions ContributionOption[]
363 services Service[]
364 admins User[] @relation("studentAssociationAdmins")
365 groupsEditors User[] @relation("studentAssociationGroupsEditor")
366 pages Page[]
367}
368
369model Contribution {
370 id String @id @default(dbgenerated("nanoid('contribution:'::text)"))
371 option ContributionOption @relation(fields: [optionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
372 optionId String
373 transaction LydiaTransaction?
374 transactionId String?
375 user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
376 paid Boolean @default(false)
377 userId String
378
379 @@unique([optionId, userId])
380}
381
382model ContributionOption {
383 id String @id @default(dbgenerated("nanoid('contributionoption:'::text)"))
384 offeredIn School @relation(fields: [offeredInId], references: [id], onDelete: Cascade, onUpdate: Cascade)
385 offeredInId String
386 paysFor StudentAssociation[]
387 beneficiary LydiaAccount? @relation(fields: [lydiaAccountId], references: [id], onDelete: SetNull, onUpdate: Cascade)
388 lydiaAccountId String?
389 name String
390 price Float
391 description String @default("") @db.Text
392 contributions Contribution[]
393}
394
395/// The different kinds of groups
396enum GroupType {
397 StudentAssociationSection
398 Association
399 Club
400 List
401 Integration
402 Group
403}
404
405/// A group is a collection of users
406model Group {
407 id String @id @default(dbgenerated("nanoid('g:'::text)"))
408 uid String @unique @db.VarChar(255)
409 parentId String?
410 /// Helper field to get a whole tree without processing all groups
411 /// To be set to the group's id itself for root groups.
412 familyId String?
413 studentAssociationId String
414 pictureFile String @default("") @db.VarChar(255)
415 pictureFileDark String @default("") @db.VarChar(255)
416 name String @db.VarChar(255)
417 type GroupType
418 color String @db.VarChar(7)
419 selfJoinable Boolean @default(false)
420 roomIsOpen Boolean @default(false)
421 createdAt DateTime @default(now())
422 updatedAt DateTime @default(now()) @updatedAt
423 unlisted Boolean @default(false)
424
425 address String @default("") @db.VarChar(255)
426 description String @default("") @db.VarChar(255)
427 email String @default("") @db.VarChar(255)
428 mailingList String @default("") @db.VarChar(255)
429 longDescription String @default("")
430 website String @default("") @db.VarChar(255)
431 ldapUid String @default("") @db.VarChar(255)
432 links Link[]
433 pages Page[]
434
435 /// Parent group, from which this group inherits its permissions
436 parent Group? @relation("parent", fields: [parentId], references: [id], onUpdate: Cascade, onDelete: Restrict)
437 children Group[] @relation("parent")
438
439 /// Family root, only created for performance reasons
440 familyRoot Group? @relation("root", fields: [familyId], references: [id], onUpdate: Cascade, onDelete: Restrict)
441 familyChildren Group[] @relation("root")
442
443 /// Related clubs
444 related Group[] @relation("related")
445 relatedTo Group[] @relation("related")
446
447 articles Article[]
448 members GroupMember[]
449 studentAssociation StudentAssociation @relation(fields: [studentAssociationId], references: [id], onUpdate: Cascade, onDelete: Restrict)
450 events Event[]
451 coOrganizedEvents Event[] @relation("coOrganizer")
452 lyiaAccounts LydiaAccount[]
453 tickets Ticket[] @relation("openTo")
454 services Service[]
455 restrictedFormSections FormSection[] @relation("restrictedTo")
456
457 notifications Notification[]
458 groupId String?
459 ticketGroupId String?
460 joinedByBookingTickets Ticket[] @relation("autojoin")
461 themes Theme[]
462 defaultApplicableOffers Promotion[] @relation("defaultApplicableOffers")
463
464 // For ldap
465 ldapGidNumber Int @unique @default(autoincrement())
466
467 // For full-text search
468 search Unsupported("tsvector") @default(dbgenerated("''::tsvector"))
469 forms Form[]
470 boardOf StudentAssociation? @relation("studentAssociationBoard")
471 boardOfId String?
472 tokens Credential[]
473
474 @@index([search], type: Gin)
475}
476
477/// The intermediate model between users
478model GroupMember {
479 groupId String
480 memberId String
481 title String @default("") @db.VarChar(255)
482 president Boolean @default(false)
483 treasurer Boolean @default(false)
484 vicePresident Boolean @default(false)
485 secretary Boolean @default(false)
486 canEditMembers Boolean @default(false)
487 canEditArticles Boolean @default(false)
488 canScanEvents Boolean @default(false)
489 isDeveloper Boolean @default(false)
490 createdAt DateTime @default(now())
491
492 group Group @relation(fields: [groupId], references: [id], onUpdate: Cascade, onDelete: Cascade)
493 member User @relation(fields: [memberId], references: [id], onUpdate: Cascade, onDelete: Cascade)
494
495 @@id([groupId, memberId])
496}
497
498/// An article is a post in a group
499model Article {
500 id String @id @default(dbgenerated("nanoid('a:'::text)"))
501 authorId String?
502 groupId String
503 slug String @db.VarChar(255) // TODO remove
504 title String @db.VarChar(255)
505 body String @db.Text
506 published Boolean @default(false)
507 visibility Visibility @default(Private)
508 createdAt DateTime @default(now())
509 notifiedAt DateTime? @default(now()) // to prevent old notifications before this was added. Another migration will remove this default value.
510 publishedAt DateTime @default(now())
511 pictureFile String @default("") @db.VarChar(255)
512 links Link[]
513
514 author User? @relation(fields: [authorId], references: [id], onUpdate: Cascade, onDelete: SetNull)
515 group Group @relation(fields: [groupId], references: [id], onUpdate: Cascade, onDelete: Cascade)
516 event Event? @relation(fields: [eventId], references: [id])
517 eventId String?
518 reactions Reaction[]
519 sharedBy User[] @relation("articleShares")
520
521 // For full-text search
522 search Unsupported("tsvector") @default(dbgenerated("''::tsvector"))
523
524 @@index([search], type: Gin)
525}
526
527enum EventFrequency {
528 Once
529 Weekly
530 Monthly
531 Biweekly
532}
533
534/// An event is a date, time and place, as well as an optional ticket
535model Event {
536 id String @id @default(dbgenerated("nanoid('e:'::text)"))
537 createdAt DateTime @default(now())
538 updatedAt DateTime @default(now()) @updatedAt
539 authorId String?
540 groupId String
541 contactMail String
542 beneficiary LydiaAccount? @relation(fields: [lydiaAccountId], references: [id])
543 description String @db.Text
544 slug String @db.VarChar(255)
545 title String @db.VarChar(255)
546 // events without dates are useful when they're "drafts", but they' can't appear in the calendar - thus they're only visible by link
547 startsAt DateTime?
548 endsAt DateTime?
549 globalCapacity Int?
550 notifiedAt DateTime? @default(now()) // to prevent old notifications before this was added. Another migration will remove this default value.
551 location String @default("") @db.VarChar(255)
552 visibility Visibility
553 frequency EventFrequency @default(Once)
554 recurringUntil DateTime?
555 pictureFile String @default("") @db.VarChar(255)
556 showPlacesLeft Boolean @default(true)
557 showCapacity Boolean @default(true)
558 managers EventManager[]
559 author User? @relation(fields: [authorId], references: [id], onUpdate: Cascade, onDelete: SetNull)
560 includeInKiosk Boolean @default(false)
561 group Group @relation(fields: [groupId], references: [id])
562 coOrganizers Group[] @relation("coOrganizer")
563 tickets Ticket[]
564 ticketGroups TicketGroup[]
565 articles Article[]
566 lydiaAccountId String?
567 links Link[]
568 bannedUsers User[] @relation("bannedFromEvents")
569 reactions Reaction[]
570 forms Form[]
571 sharedBy User[] @relation("eventShares")
572 applicableOffers Promotion[]
573 managerInvites EventManagerInvite[]
574 // enfore providing a point of contact for external bookings
575 enforcePointOfContact Boolean @default(false)
576
577 // For full-text search
578 search Unsupported("tsvector") @default(dbgenerated("''::tsvector"))
579
580 @@index([search], type: Gin)
581}
582
583enum Visibility {
584 Private
585 Unlisted
586 GroupRestricted
587 SchoolRestricted
588 Public
589}
590
591/// Used to make invite links to invite people as managers of an event
592model EventManagerInvite {
593 id String @id @default(dbgenerated("nanoid('eminvite:'::text)"))
594 createdAt DateTime @default(now())
595 updatedAt DateTime @updatedAt
596
597 /// Maximum number of uses
598 capacity Int?
599 code String @unique
600 /// If null, should expire at the event's end date, not never expire
601 expiresAt DateTime?
602
603 eventId String
604 event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
605
606 canVerifyRegistrations Boolean @default(true) // Can scan tickets
607 canEdit Boolean @default(false)
608 canEditPermissions Boolean @default(false)
609
610 usedBy User[] @relation("eventManagerInviteUses")
611}
612
613/// An event manager is a user that can scan tickets, and may be able to manage the event
614model EventManager {
615 id String @id @default(dbgenerated("nanoid('em:'::text)"))
616 eventId String
617 userId String
618 canVerifyRegistrations Boolean @default(true) // Can scan tickets
619 canEdit Boolean @default(false)
620 canEditPermissions Boolean @default(false)
621
622 event Event @relation(fields: [eventId], references: [id], onUpdate: Cascade, onDelete: Cascade)
623 user User @relation(fields: [userId], references: [id], onUpdate: Cascade, onDelete: Cascade)
624
625 @@unique([eventId, userId])
626}
627
628/// A ticket group allows for conditions on multiple tickets, such as an upper limit on the sum of registrations in the sub-tickets
629model TicketGroup {
630 id String @id @default(dbgenerated("nanoid('tg:'::text)"))
631 eventId String
632 name String @db.VarChar(255)
633
634 capacity Int @default(0) // 0 means unlimited, capacity is on the sum of sub-tickets registrations
635 tickets Ticket[]
636 event Event @relation(fields: [eventId], references: [id])
637
638 @@unique([eventId, name])
639}
640
641/// Decide when to count a ticket as counting towards the capacity
642enum TicketCountingPolicy {
643 OnBooked // Count when the ticket is booked
644 OnPaid // Count when the ticket is paid
645}
646
647/// A ticket is a way to register for an event. May include a price and conditions.
648model Ticket {
649 id String @id @default(dbgenerated("nanoid('t:'::text)"))
650 slug String
651 eventId String
652 ticketGroupId String?
653 name String @db.VarChar(255)
654 description String @db.VarChar(255)
655 opensAt DateTime?
656 closesAt DateTime?
657 minimumPrice Float
658 maximumPrice Float
659 capacity Int?
660 registrations Registration[]
661 links Link[]
662 allowedPaymentMethods PaymentMethod[] @default([]) // empty means all
663 countingPolicy TicketCountingPolicy @default(OnBooked)
664 inviteCode String? @unique
665
666 // Conditions for that ticket.
667 openToPromotions Int[] @default([])
668 openToAlumni Boolean? @default(false) // false means only non-alumni, true means only alumni, null means both
669 openToExternal Boolean? @default(false) // same thing
670 openToContributors Boolean? // same thing
671 openToApprentices Boolean? // same thing
672 openToSchools School[]
673 openToMajors Major[]
674 openToGroups Group[] @relation("openTo")
675 godsonLimit Int @default(0) // 0 means unlimited
676 autojoinGroups Group[] @relation("autojoin")
677
678 onlyManagersCanProvide Boolean @default(false)
679
680 event Event @relation(fields: [eventId], references: [id], onUpdate: Cascade, onDelete: Cascade)
681 group TicketGroup? @relation(fields: [ticketGroupId], references: [id], onDelete: SetNull, onUpdate: Cascade)
682 Promotion Promotion? @relation(fields: [promotionId], references: [id])
683 promotionId String?
684 invited User[] @relation("invitedTo")
685}
686
687/// A reservation is a user's registration for a ticket
688model Registration {
689 id String @id @default(dbgenerated("nanoid('r:'::text)"))
690 ticketId String
691 externalBeneficiary String?
692 internalBeneficiary User? @relation(fields: [internalBeneficiaryId], references: [id], onUpdate: Cascade, onDelete: SetNull, name: "beneficiaryOf")
693 internalBeneficiaryId String?
694 authorId String?
695 authorEmail String @default("")
696 authorName String @default("")
697 verifiedById String?
698 opposedById String?
699 cancelledById String?
700 createdAt DateTime @default(now())
701 updatedAt DateTime @updatedAt
702 paymentMethod PaymentMethod?
703 paid Boolean @default(false)
704 wantsToPay Float?
705 // Person to refer to, especially for external beneficiaries, to handle misconducts at the event
706 pointOfContactId String?
707 pointOfContact User? @relation(fields: [pointOfContactId], references: [id], onDelete: SetNull, name: "pointOfContactFor")
708
709 lydiaTransaction LydiaTransaction?
710 paypalTransaction PaypalTransaction?
711 ticket Ticket @relation(fields: [ticketId], references: [id], onUpdate: Cascade, onDelete: Cascade)
712 author User? @relation(name: "author", fields: [authorId], references: [id], onUpdate: Cascade, onDelete: SetNull)
713 verifiedBy User? @relation(name: "verifiedBy", fields: [verifiedById], references: [id], onUpdate: Cascade, onDelete: SetNull)
714 verifiedAt DateTime?
715 opposedBy User? @relation(name: "opposedBy", fields: [opposedById], references: [id], onUpdate: Cascade, onDelete: SetNull)
716 opposedAt DateTime?
717 cancelledBy User? @relation(name: "cancelledBy", fields: [cancelledById], references: [id], onUpdate: Cascade, onDelete: SetNull)
718 cancelledAt DateTime?
719 seenBy User[] @relation(name: "seenBy")
720 formAnswer Answer?
721
722 // Those are kept in sync by a trigger, and are used for fuzzy-matching in a full text search
723 authorFullName String? @default("") @db.VarChar(255)
724 internalBeneficiaryFullName String? @default("") @db.VarChar(255)
725 search Unsupported("tsvector") @default(dbgenerated("''::tsvector"))
726
727 @@index([search], type: Gin)
728}
729
730enum PaymentMethod {
731 Lydia
732 PayPal
733 Card
734 Transfer
735 Check
736 Cash
737 External
738 Other
739}
740
741/// A log entry is a log of an action that happened on the website
742model LogEntry {
743 id String @id @default(dbgenerated("nanoid('log:'::text)"))
744 happenedAt DateTime @default(now())
745 userId String?
746 area String @db.VarChar(255) // billeterie, gestion clubs, etc. à typer, mais pas dans la DB (pour être plus flexible)
747 action String @db.VarChar(255)
748 target String? @db.VarChar(255)
749 message String @db.Text
750
751 user User? @relation(fields: [userId], references: [id], onUpdate: Cascade, onDelete: SetNull)
752}
753
754/// A Lydia account
755model LydiaAccount {
756 id String @id @default(dbgenerated("nanoid('lydia:'::text)"))
757 groupId String?
758 group Group? @relation(fields: [groupId], references: [id], onUpdate: Cascade, onDelete: Cascade)
759 name String @default("") @db.VarChar(255)
760 privateToken String @default("") @db.VarChar(255)
761 vendorToken String @default("") @db.VarChar(255)
762 studentAssociationId String?
763 events Event[]
764 studentAssociation StudentAssociation? @relation(fields: [studentAssociationId], references: [id])
765 ContributionOption ContributionOption[]
766
767 @@unique([privateToken, vendorToken, groupId])
768}
769
770// Lydia payment
771model LydiaTransaction {
772 id String @id @default(dbgenerated("nanoid('lydiapayment:'::text)"))
773 phoneNumber String @default("") @db.VarChar(255)
774 registrationId String? @unique
775 registration Registration? @relation(fields: [registrationId], references: [id], onUpdate: Cascade, onDelete: Cascade)
776 shopPaymentId String? @unique
777 requestId String?
778 requestUuid String?
779 transactionId String?
780 createdAt DateTime @default(now())
781 updatedAt DateTime @updatedAt
782 contribution Contribution? @relation(fields: [studentAssociationContributionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
783 studentAssociationContributionId String? @unique
784 paidCallback String?
785}
786
787enum PayPalTransactionStatus {
788 Created
789 Saved
790 Approved
791 Voided
792 Completed
793 PayerActionRequired
794}
795
796/// Paypal payment
797model PaypalTransaction {
798 id String @id @default(dbgenerated("nanoid('paypalpayment:'::text)"))
799 emailAddress String @default("") @db.VarChar(255)
800 registrationId String? @unique
801 registration Registration? @relation(fields: [registrationId], references: [id], onUpdate: Cascade, onDelete: Cascade)
802 status PayPalTransactionStatus?
803
804 /// PayPal's order ID.
805 orderId String?
806 createdAt DateTime @default(now())
807 updatedAt DateTime @updatedAt
808}
809
810/// A NotificationSubscription stores a user's subscription to push notifications on a user agent
811model NotificationSubscription {
812 id String @id @default(dbgenerated("nanoid('notifsub:'::text)"))
813 name String @default("") @db.VarChar(255)
814 createdAt DateTime @default(now())
815 updatedAt DateTime @updatedAt
816 owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade, onUpdate: Cascade)
817 ownerId String
818 endpoint String @unique
819 expiresAt DateTime?
820 authKey String
821 p256dhKey String
822 notifications Notification[]
823}
824
825/// A notification is a push notification that was sent to a user
826model Notification {
827 id String @id @default(dbgenerated("nanoid('notif:'::text)"))
828 createdAt DateTime @default(now())
829 updatedAt DateTime @updatedAt
830 timestamp DateTime?
831 subscription NotificationSubscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
832 subscriptionId String
833 image String @default("")
834 actions Link[]
835 title String @db.VarChar(255)
836 imageFile String @default("")
837 body String @db.Text
838 vibrate Int[] @default([])
839 group Group? @relation(fields: [groupId], references: [id], onDelete: SetNull, onUpdate: Cascade)
840 groupId String?
841 channel NotificationChannel @default(Other)
842 user User? @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
843 userId String?
844 goto String @default("")
845}
846
847/// A NotificationChannel represents the different kinds of reasons why you might receive a notification: a shotgun just opened, etc.
848enum NotificationChannel {
849 Articles
850 Shotguns
851 Permissions
852 GroupBoard
853 GodparentRequests
854 Comments
855 Mandatory
856 Other // should't be used too much
857}
858
859/// Announcement is a way to get a message accross the entire site, such as for maintenance announcements.
860model Announcement {
861 id String @id @default(dbgenerated("nanoid('ann:'::text)"))
862 by User? @relation(fields: [userId], references: [id], onDelete: SetNull, onUpdate: Cascade)
863 userId String?
864 title String @db.VarChar(255)
865 body String @db.Text
866 warning Boolean
867 createdAt DateTime @default(now())
868 updatedAt DateTime @updatedAt
869 startsAt DateTime
870 endsAt DateTime
871}
872
873model TeachingUnit {
874 id String @id @default(dbgenerated("nanoid('ue:'::text)"))
875 name String @db.VarChar(255)
876 shortName String @default("") @db.VarChar(255)
877 apogeeCode String? @db.VarChar(255)
878 subjects Subject[]
879}
880
881model Subject {
882 id String @id @default(dbgenerated("nanoid('subj:'::text)"))
883 name String
884 slug String
885 shortName String @default("") @db.VarChar(255)
886 yearTier Int?
887 forApprentices Boolean @default(false)
888 links Link[]
889 apogeeCode String? @db.VarChar(255)
890 unitId String?
891 unit TeachingUnit? @relation(fields: [unitId], references: [id], onDelete: SetNull, onUpdate: Cascade)
892 nextExamAt DateTime?
893 majors Major[]
894 minors Minor[]
895 documents Document[]
896 // 1 for the first semester, 2 for the second one. Null means both
897 semester Int?
898 emoji String @default("") @db.VarChar(4)
899
900 @@unique([slug, yearTier, forApprentices])
901}
902
903enum DocumentType {
904 // Graded
905 Exam // Partiel
906 PracticalExam // BE
907 GradedExercises // DM
908
909 // Ungraded
910 Exercises // TD
911 Practical // TP
912 CourseNotes // CM
913 CourseSlides // Diapos
914 Summary // Fiche de rev, Fiche
915
916 Miscellaneous // Divers
917}
918
919model Document {
920 id String @id @default(dbgenerated("nanoid('doc:'::text)"))
921 slug String
922 createdAt DateTime @default(now())
923 updatedAt DateTime @updatedAt
924 schoolYear Int // stored using the school year start's year
925
926 title String
927 description String @db.Text
928 // Null subject means the document needs to be sorted
929 subject Subject? @relation(fields: [subjectId], references: [id], onDelete: Cascade, onUpdate: Cascade)
930 subjectId String?
931 type DocumentType
932 paperPaths String[] // le sujet
933 solutionPaths String[] // la correction
934
935 uploader User? @relation(fields: [uploaderId], references: [id], onDelete: SetNull, onUpdate: Cascade)
936 uploaderId String?
937
938 reactions Reaction[]
939
940 search Unsupported("tsvector") @default(dbgenerated("''::tsvector"))
941
942 @@unique([subjectId, slug])
943 @@index([search], type: Gin)
944}
945
946model Reaction {
947 id String @id @default(dbgenerated("nanoid('reac:'::text)"))
948 emoji String
949
950 createdAt DateTime @default(now())
951 updatedAt DateTime @updatedAt
952 author User? @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)
953 authorId String?
954
955 document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade, onUpdate: Cascade)
956 documentId String?
957 article Article? @relation(fields: [articleId], references: [id], onDelete: Cascade, onUpdate: Cascade)
958 articleId String?
959 event Event? @relation(fields: [eventId], references: [id], onDelete: Cascade, onUpdate: Cascade)
960 eventId String?
961
962 @@unique([emoji, authorId, documentId])
963 @@unique([emoji, authorId, articleId])
964 @@unique([emoji, authorId, eventId])
965}
966
967enum PromotionType {
968 SIMPPS
969 // More to come...
970}
971
972model PromotionCode {
973 id String @id @default(dbgenerated("nanoid('promocode:'::text)"))
974 createdAt DateTime @default(now())
975 updatedAt DateTime @updatedAt
976 claimedAt DateTime?
977 code String @unique
978 claimedBy User? @relation(fields: [claimedById], references: [id], onDelete: Cascade)
979 claimedById String?
980 promotion Promotion @relation(fields: [promotionId], references: [id], onDelete: Cascade)
981 promotionId String
982}
983
984model Promotion {
985 id String @id @default(dbgenerated("nanoid('promo:'::text)"))
986 createdAt DateTime @default(now())
987 updatedAt DateTime @updatedAt
988 validUntil DateTime?
989 type PromotionType
990 codes PromotionCode[]
991 validOn Ticket[]
992 validByDefaultOn Group[] @relation("defaultApplicableOffers")
993
994 priceOverride Int
995 events Event[]
996}
997
998model Form {
999 id String @id @default(dbgenerated("nanoid('form:'::text)"))
1000 createdAt DateTime @default(now())
1001 updatedAt DateTime @updatedAt
1002 createdBy User? @relation(fields: [createdById], references: [id], onDelete: SetNull, onUpdate: Cascade)
1003 createdById String?
1004 visibility Visibility
1005 groupId String?
1006 group Group? @relation(fields: [groupId], references: [id], onDelete: Cascade, onUpdate: Cascade)
1007
1008 event Event? @relation(fields: [eventId], references: [id], onDelete: SetNull, onUpdate: Cascade)
1009 eventId String?
1010
1011 opensAt DateTime?
1012 closesAt DateTime?
1013 title String @db.VarChar(255)
1014 description String @db.Text
1015 sections FormSection[]
1016 answeredBy User[] @relation("completedForms")
1017 partiallyAnsweredBy User[] @relation("partiallyCompletedForms")
1018 allowEditingAnswers Boolean @default(true)
1019 // Pretty specific use-case: manually marking or un-marking answers from the answer list for each user. Used for votes that can be both online and offline.
1020 enableAnswersCompletionCheckbox Boolean @default(false)
1021 markedCheckboxes User[] @relation("formsWithMarkedCheckbox")
1022 restrictToPromotions Int[]
1023 contributorsOnly Boolean @default(false)
1024
1025 // For full-text search
1026 search Unsupported("tsvector") @default(dbgenerated("''::tsvector"))
1027 linkedGoogleSheetId String?
1028
1029 @@index([search], type: Gin)
1030}
1031
1032model FormSection {
1033 id String @id @default(dbgenerated("nanoid('formsection:'::text)"))
1034 order Int
1035 form Form @relation(fields: [formId], references: [id], onDelete: Cascade)
1036 formId String
1037 title String @db.VarChar(255)
1038 description String @db.Text
1039 questions Question[]
1040
1041 /// Conditions
1042
1043 restrictedToGroups Group[] @relation("restrictedTo")
1044 jumps FormJump[]
1045
1046 @@unique([formId, order])
1047}
1048
1049/// Represent a jump condition: answering a certain value of a certain question changes what section of the form is shown next
1050model FormJump {
1051 id String @id @default(dbgenerated("nanoid('formjump:'::text)"))
1052 question Question @relation(fields: [questionId], references: [id], onDelete: Cascade)
1053 questionId String
1054 value String
1055 target FormSection @relation(fields: [targetId], references: [id], onDelete: Cascade, onUpdate: Cascade)
1056 targetId String
1057}
1058
1059model Question {
1060 id String @id @default(dbgenerated("nanoid('question:'::text)"))
1061 section FormSection @relation(fields: [sectionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
1062 sectionId String
1063 order Int
1064
1065 title String @db.VarChar(255)
1066 description String @db.Text
1067 type QuestionKind
1068 mandatory Boolean @default(false)
1069 anonymous Boolean @default(false)
1070
1071 options String[] @default([])
1072 scaleStart Int?
1073 scaleEnd Int?
1074 allowOptionOther Boolean @default(false)
1075 allowedFiletypes String[] @default([]) // empty means all
1076
1077 // See Answer
1078 defaultAnswer String[]
1079
1080 answers Answer[]
1081 jumps FormJump[]
1082
1083 @@unique([sectionId, order])
1084}
1085
1086enum QuestionKind {
1087 Text
1088 LongText
1089 SelectOne
1090 SelectMultiple
1091 FileUpload
1092 // In scale, options has two values, the labels of the start and end of the scale
1093 Scale
1094 Number
1095 Date
1096 Time
1097}
1098
1099model Answer {
1100 id String @id @default(dbgenerated("nanoid('answer:'::text)"))
1101 question Question @relation(fields: [questionId], references: [id], onDelete: Cascade)
1102 questionId String
1103
1104 createdAt DateTime @default(now())
1105 updatedAt DateTime @updatedAt
1106
1107 createdBy User? @relation(fields: [createdById], references: [id], onDelete: Cascade, onUpdate: Cascade)
1108 createdById String?
1109
1110 /// Only the first value is considered for all types except SelectMultiple.
1111 /// Can be empty if the user didn't answer the question (when it wasn't mandatory, for example)
1112 answer String[]
1113
1114 /// Data to link back to an event booking
1115 booking Registration? @relation(fields: [bookingId], references: [id], onDelete: SetNull, onUpdate: Cascade)
1116 bookingId String? @unique
1117
1118 // For full-text search
1119 search Unsupported("tsvector") @default(dbgenerated("''::tsvector"))
1120
1121 // For now, users can answer only once to every question
1122 @@unique([questionId, createdById])
1123 @@index([search], type: Gin)
1124}
1125
1126/// A user-defined markdown page, useful for custom pages, only liked to student associations for now.
1127model Page {
1128 id String @id @default(dbgenerated("nanoid('page:'::text)"))
1129
1130 // Paths are unique per linked resource, final URL of the page should therefore be namespaced
1131 path String @db.VarChar(255)
1132 title String @db.VarChar(255)
1133 body String @db.Text
1134 createdAt DateTime @default(now())
1135 updatedAt DateTime @updatedAt
1136 lastAuthor User? @relation(fields: [lastAuthorId], references: [id], onDelete: SetNull, onUpdate: Cascade)
1137 lastAuthorId String?
1138 studentAssociation StudentAssociation? @relation(fields: [studentAssociationId], references: [id], onDelete: Cascade, onUpdate: Cascade)
1139 studentAssociationId String?
1140 group Group? @relation(fields: [groupId], references: [id], onDelete: SetNull, onUpdate: Cascade)
1141 groupId String?
1142 /// Paths to files that are included on that page (relative to the storage root as always)
1143 files String[]
1144 // For full-text search
1145 search Unsupported("tsvector") @default(dbgenerated("''::tsvector"))
1146
1147 @@unique([studentAssociationId, path])
1148 @@unique([groupId, path])
1149 @@index([search], type: Gin)
1150}
1151
1152// UIDs that should never be considered free
1153model BlockedUid {
1154 uid String @id
1155}
1156
1157model Theme {
1158 id String @id @default(dbgenerated("nanoid('theme:'::text)"))
1159 name String @db.VarChar(255)
1160 values ThemeValue[]
1161 createdAt DateTime @default(now())
1162 updatedAt DateTime @updatedAt
1163 visibility Visibility @default(Private)
1164 startsAt DateTime? @default(now())
1165 endsAt DateTime?
1166 author Group? @relation(fields: [authorId], references: [id], onDelete: SetNull, onUpdate: Cascade)
1167 authorId String?
1168
1169 // Auto-change people's theme to this one when it's visible to them
1170 autodeploy Boolean @default(false)
1171 blockedBy User[]
1172}
1173
1174enum ThemeVariant {
1175 Light
1176 Dark
1177 // Might add more stuff, like "High contrast light, high contrast dark", etc
1178}
1179
1180enum ThemeVariable {
1181 ColorBackground
1182 ColorBackground2
1183 ColorBackground3
1184 ColorBackground4
1185 ColorShy
1186 ColorMuted
1187 ColorForeground
1188 ColorPrimary
1189 ColorSuccess
1190 ColorDanger
1191 ColorWarning
1192 ColorPrimaryBackground
1193 ColorSuccessBackground
1194 ColorDangerBackground
1195 ColorWarningBackground
1196 ImageLogoNavbarTop
1197 ImageLogoNavbarSide
1198 ImageBackgroundNavbarBottom
1199 ImageBackgroundNavbarTop
1200 PatternBackground
1201}
1202
1203model ThemeValue {
1204 id String @id @default(dbgenerated("nanoid('themeval:'::text)"))
1205 variable ThemeVariable
1206 value String @db.VarChar(500)
1207 theme Theme @relation(fields: [themeId], references: [id], onDelete: Cascade, onUpdate: Cascade)
1208 themeId String
1209 variant ThemeVariant @default(Light)
1210
1211 @@unique([themeId, variant, variable])
1212}