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, 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 Mandatory
855 Other // should't be used too much
856}
857
858/// Announcement is a way to get a message accross the entire site, such as for maintenance announcements.
859model Announcement {
860 id String @id @default(dbgenerated("nanoid('ann:'::text)"))
861 by User? @relation(fields: [userId], references: [id], onDelete: SetNull, onUpdate: Cascade)
862 userId String?
863 title String @db.VarChar(255)
864 body String @db.Text
865 warning Boolean
866 createdAt DateTime @default(now())
867 updatedAt DateTime @updatedAt
868 startsAt DateTime
869 endsAt DateTime
870}
871
872model TeachingUnit {
873 id String @id @default(dbgenerated("nanoid('ue:'::text)"))
874 name String @db.VarChar(255)
875 shortName String @default("") @db.VarChar(255)
876 apogeeCode String? @db.VarChar(255)
877 subjects Subject[]
878}
879
880model Subject {
881 id String @id @default(dbgenerated("nanoid('subj:'::text)"))
882 name String
883 slug String
884 shortName String @default("") @db.VarChar(255)
885 yearTier Int?
886 forApprentices Boolean @default(false)
887 links Link[]
888 apogeeCode String? @db.VarChar(255)
889 unitId String?
890 unit TeachingUnit? @relation(fields: [unitId], references: [id], onDelete: SetNull, onUpdate: Cascade)
891 nextExamAt DateTime?
892 majors Major[]
893 minors Minor[]
894 documents Document[]
895 // 1 for the first semester, 2 for the second one. Null means both
896 semester Int?
897 emoji String @default("") @db.VarChar(4)
898
899 @@unique([slug, yearTier, forApprentices])
900}
901
902enum DocumentType {
903 // Graded
904 Exam // Partiel
905 PracticalExam // BE
906 GradedExercises // DM
907
908 // Ungraded
909 Exercises // TD
910 Practical // TP
911 CourseNotes // CM
912 CourseSlides // Diapos
913 Summary // Fiche de rev, Fiche
914
915 Miscellaneous // Divers
916}
917
918model Document {
919 id String @id @default(dbgenerated("nanoid('doc:'::text)"))
920 slug String
921 createdAt DateTime @default(now())
922 updatedAt DateTime @updatedAt
923 schoolYear Int // stored using the school year start's year
924
925 title String
926 description String @db.Text
927 // Null subject means the document needs to be sorted
928 subject Subject? @relation(fields: [subjectId], references: [id], onDelete: Cascade, onUpdate: Cascade)
929 subjectId String?
930 type DocumentType
931 paperPaths String[] // le sujet
932 solutionPaths String[] // la correction
933
934 uploader User? @relation(fields: [uploaderId], references: [id], onDelete: SetNull, onUpdate: Cascade)
935 uploaderId String?
936
937 reactions Reaction[]
938
939 search Unsupported("tsvector") @default(dbgenerated("''::tsvector"))
940
941 @@unique([subjectId, slug])
942 @@index([search], type: Gin)
943}
944
945model Reaction {
946 id String @id @default(dbgenerated("nanoid('reac:'::text)"))
947 emoji String
948
949 createdAt DateTime @default(now())
950 updatedAt DateTime @updatedAt
951 author User? @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)
952 authorId String?
953
954 document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade, onUpdate: Cascade)
955 documentId String?
956 article Article? @relation(fields: [articleId], references: [id], onDelete: Cascade, onUpdate: Cascade)
957 articleId String?
958 event Event? @relation(fields: [eventId], references: [id], onDelete: Cascade, onUpdate: Cascade)
959 eventId String?
960
961 @@unique([emoji, authorId, documentId])
962 @@unique([emoji, authorId, articleId])
963 @@unique([emoji, authorId, eventId])
964}
965
966enum PromotionType {
967 SIMPPS
968 // More to come...
969}
970
971model PromotionCode {
972 id String @id @default(dbgenerated("nanoid('promocode:'::text)"))
973 createdAt DateTime @default(now())
974 updatedAt DateTime @updatedAt
975 claimedAt DateTime?
976 code String @unique
977 claimedBy User? @relation(fields: [claimedById], references: [id], onDelete: Cascade)
978 claimedById String?
979 promotion Promotion @relation(fields: [promotionId], references: [id], onDelete: Cascade)
980 promotionId String
981}
982
983model Promotion {
984 id String @id @default(dbgenerated("nanoid('promo:'::text)"))
985 createdAt DateTime @default(now())
986 updatedAt DateTime @updatedAt
987 validUntil DateTime?
988 type PromotionType
989 codes PromotionCode[]
990 validOn Ticket[]
991 validByDefaultOn Group[] @relation("defaultApplicableOffers")
992
993 priceOverride Int
994 events Event[]
995}
996
997model Form {
998 id String @id @default(dbgenerated("nanoid('form:'::text)"))
999 createdAt DateTime @default(now())
1000 updatedAt DateTime @updatedAt
1001 createdBy User? @relation(fields: [createdById], references: [id], onDelete: SetNull, onUpdate: Cascade)
1002 createdById String?
1003 visibility Visibility
1004 groupId String?
1005 group Group? @relation(fields: [groupId], references: [id], onDelete: Cascade, onUpdate: Cascade)
1006
1007 event Event? @relation(fields: [eventId], references: [id], onDelete: SetNull, onUpdate: Cascade)
1008 eventId String?
1009
1010 opensAt DateTime?
1011 closesAt DateTime?
1012 title String @db.VarChar(255)
1013 description String @db.Text
1014 sections FormSection[]
1015 answeredBy User[] @relation("completedForms")
1016 partiallyAnsweredBy User[] @relation("partiallyCompletedForms")
1017 allowEditingAnswers Boolean @default(true)
1018 // 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.
1019 enableAnswersCompletionCheckbox Boolean @default(false)
1020 markedCheckboxes User[] @relation("formsWithMarkedCheckbox")
1021 restrictToPromotions Int[]
1022 contributorsOnly Boolean @default(false)
1023
1024 // For full-text search
1025 search Unsupported("tsvector") @default(dbgenerated("''::tsvector"))
1026 linkedGoogleSheetId String?
1027
1028 @@index([search], type: Gin)
1029}
1030
1031model FormSection {
1032 id String @id @default(dbgenerated("nanoid('formsection:'::text)"))
1033 order Int
1034 form Form @relation(fields: [formId], references: [id], onDelete: Cascade)
1035 formId String
1036 title String @db.VarChar(255)
1037 description String @db.Text
1038 questions Question[]
1039
1040 /// Conditions
1041
1042 restrictedToGroups Group[] @relation("restrictedTo")
1043 jumps FormJump[]
1044
1045 @@unique([formId, order])
1046}
1047
1048/// Represent a jump condition: answering a certain value of a certain question changes what section of the form is shown next
1049model FormJump {
1050 id String @id @default(dbgenerated("nanoid('formjump:'::text)"))
1051 question Question @relation(fields: [questionId], references: [id], onDelete: Cascade)
1052 questionId String
1053 value String
1054 target FormSection @relation(fields: [targetId], references: [id], onDelete: Cascade, onUpdate: Cascade)
1055 targetId String
1056}
1057
1058model Question {
1059 id String @id @default(dbgenerated("nanoid('question:'::text)"))
1060 section FormSection @relation(fields: [sectionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
1061 sectionId String
1062 order Int
1063
1064 title String @db.VarChar(255)
1065 description String @db.Text
1066 type QuestionKind
1067 mandatory Boolean @default(false)
1068 anonymous Boolean @default(false)
1069
1070 options String[] @default([])
1071 scaleStart Int?
1072 scaleEnd Int?
1073 allowOptionOther Boolean @default(false)
1074 allowedFiletypes String[] @default([]) // empty means all
1075
1076 // See Answer
1077 defaultAnswer String[]
1078
1079 answers Answer[]
1080 jumps FormJump[]
1081
1082 @@unique([sectionId, order])
1083}
1084
1085enum QuestionKind {
1086 Text
1087 LongText
1088 SelectOne
1089 SelectMultiple
1090 FileUpload
1091 // In scale, options has two values, the labels of the start and end of the scale
1092 Scale
1093 Number
1094 Date
1095 Time
1096}
1097
1098model Answer {
1099 id String @id @default(dbgenerated("nanoid('answer:'::text)"))
1100 question Question @relation(fields: [questionId], references: [id], onDelete: Cascade)
1101 questionId String
1102
1103 createdAt DateTime @default(now())
1104 updatedAt DateTime @updatedAt
1105
1106 createdBy User? @relation(fields: [createdById], references: [id], onDelete: Cascade, onUpdate: Cascade)
1107 createdById String?
1108
1109 /// Only the first value is considered for all types except SelectMultiple.
1110 /// Can be empty if the user didn't answer the question (when it wasn't mandatory, for example)
1111 answer String[]
1112
1113 /// Data to link back to an event booking
1114 booking Registration? @relation(fields: [bookingId], references: [id], onDelete: SetNull, onUpdate: Cascade)
1115 bookingId String? @unique
1116
1117 // For full-text search
1118 search Unsupported("tsvector") @default(dbgenerated("''::tsvector"))
1119
1120 // For now, users can answer only once to every question
1121 @@unique([questionId, createdById])
1122 @@index([search], type: Gin)
1123}
1124
1125/// A user-defined markdown page, useful for custom pages, only liked to student associations for now.
1126model Page {
1127 id String @id @default(dbgenerated("nanoid('page:'::text)"))
1128
1129 // Paths are unique per linked resource, final URL of the page should therefore be namespaced
1130 path String @db.VarChar(255)
1131 title String @db.VarChar(255)
1132 body String @db.Text
1133 createdAt DateTime @default(now())
1134 updatedAt DateTime @updatedAt
1135 lastAuthor User? @relation(fields: [lastAuthorId], references: [id], onDelete: SetNull, onUpdate: Cascade)
1136 lastAuthorId String?
1137 studentAssociation StudentAssociation? @relation(fields: [studentAssociationId], references: [id], onDelete: Cascade, onUpdate: Cascade)
1138 studentAssociationId String?
1139 group Group? @relation(fields: [groupId], references: [id], onDelete: SetNull, onUpdate: Cascade)
1140 groupId String?
1141 /// Paths to files that are included on that page (relative to the storage root as always)
1142 files String[]
1143 // For full-text search
1144 search Unsupported("tsvector") @default(dbgenerated("''::tsvector"))
1145
1146 @@unique([studentAssociationId, path])
1147 @@unique([groupId, path])
1148 @@index([search], type: Gin)
1149}
1150
1151// UIDs that should never be considered free
1152model BlockedUid {
1153 uid String @id
1154}
1155
1156model Theme {
1157 id String @id @default(dbgenerated("nanoid('theme:'::text)"))
1158 name String @db.VarChar(255)
1159 values ThemeValue[]
1160 createdAt DateTime @default(now())
1161 updatedAt DateTime @updatedAt
1162 visibility Visibility @default(Private)
1163 startsAt DateTime? @default(now())
1164 endsAt DateTime?
1165 author Group? @relation(fields: [authorId], references: [id], onDelete: SetNull, onUpdate: Cascade)
1166 authorId String?
1167
1168 // Auto-change people's theme to this one when it's visible to them
1169 autodeploy Boolean @default(false)
1170 blockedBy User[]
1171}
1172
1173enum ThemeVariant {
1174 Light
1175 Dark
1176 // Might add more stuff, like "High contrast light, high contrast dark", etc
1177}
1178
1179enum ThemeVariable {
1180 ColorBackground
1181 ColorBackground2
1182 ColorBackground3
1183 ColorBackground4
1184 ColorShy
1185 ColorMuted
1186 ColorForeground
1187 ColorPrimary
1188 ColorSuccess
1189 ColorDanger
1190 ColorWarning
1191 ColorPrimaryBackground
1192 ColorSuccessBackground
1193 ColorDangerBackground
1194 ColorWarningBackground
1195 ImageLogoNavbarTop
1196 ImageLogoNavbarSide
1197 ImageBackgroundNavbarBottom
1198 ImageBackgroundNavbarTop
1199 PatternBackground
1200}
1201
1202model ThemeValue {
1203 id String @id @default(dbgenerated("nanoid('themeval:'::text)"))
1204 variable ThemeVariable
1205 value String @db.VarChar(500)
1206 theme Theme @relation(fields: [themeId], references: [id], onDelete: Cascade, onUpdate: Cascade)
1207 themeId String
1208 variant ThemeVariant @default(Light)
1209
1210 @@unique([themeId, variant, variable])
1211}