@studious-lms/server 1.4.6 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist/models/access.d.ts +112 -0
  2. package/dist/models/access.d.ts.map +1 -0
  3. package/dist/models/access.js +49 -0
  4. package/dist/models/access.js.map +1 -0
  5. package/dist/models/announcement.d.ts +17 -2
  6. package/dist/models/announcement.d.ts.map +1 -1
  7. package/dist/models/announcement.js +21 -4
  8. package/dist/models/announcement.js.map +1 -1
  9. package/dist/models/assignment.d.ts +37 -37
  10. package/dist/models/assignment.d.ts.map +1 -1
  11. package/dist/models/assignment.js +15 -15
  12. package/dist/models/assignment.js.map +1 -1
  13. package/dist/models/attendance.d.ts +1 -1
  14. package/dist/models/attendance.d.ts.map +1 -1
  15. package/dist/models/attendance.js +6 -5
  16. package/dist/models/attendance.js.map +1 -1
  17. package/dist/models/auth.d.ts +5 -5
  18. package/dist/models/auth.d.ts.map +1 -1
  19. package/dist/models/auth.js +7 -11
  20. package/dist/models/auth.js.map +1 -1
  21. package/dist/models/class.d.ts +26 -26
  22. package/dist/models/class.d.ts.map +1 -1
  23. package/dist/models/class.js +7 -11
  24. package/dist/models/class.js.map +1 -1
  25. package/dist/models/comment.d.ts +134 -70
  26. package/dist/models/comment.d.ts.map +1 -1
  27. package/dist/models/comment.js +30 -42
  28. package/dist/models/comment.js.map +1 -1
  29. package/dist/models/conversation.d.ts +7 -7
  30. package/dist/models/event.d.ts +52 -3
  31. package/dist/models/event.d.ts.map +1 -1
  32. package/dist/models/event.js +56 -6
  33. package/dist/models/event.js.map +1 -1
  34. package/dist/models/file.d.ts +178 -103
  35. package/dist/models/file.d.ts.map +1 -1
  36. package/dist/models/file.js +54 -14
  37. package/dist/models/file.js.map +1 -1
  38. package/dist/models/folder.d.ts +22 -22
  39. package/dist/models/folder.d.ts.map +1 -1
  40. package/dist/models/folder.js +5 -4
  41. package/dist/models/folder.js.map +1 -1
  42. package/dist/models/labChat.d.ts +20 -20
  43. package/dist/models/labChat.d.ts.map +1 -1
  44. package/dist/models/labChat.js +8 -10
  45. package/dist/models/labChat.js.map +1 -1
  46. package/dist/models/marketing.d.ts +1 -1
  47. package/dist/models/message.d.ts +6 -6
  48. package/dist/models/newtonChat.d.ts +5 -5
  49. package/dist/models/school.d.ts +361 -0
  50. package/dist/models/school.d.ts.map +1 -0
  51. package/dist/models/school.js +314 -0
  52. package/dist/models/school.js.map +1 -0
  53. package/dist/models/section.d.ts +1 -1
  54. package/dist/models/section.d.ts.map +1 -1
  55. package/dist/models/section.js +5 -4
  56. package/dist/models/section.js.map +1 -1
  57. package/dist/models/user.d.ts +4 -4
  58. package/dist/models/worksheet.d.ts +65 -65
  59. package/dist/models/worksheet.d.ts.map +1 -1
  60. package/dist/models/worksheet.js +6 -14
  61. package/dist/models/worksheet.js.map +1 -1
  62. package/dist/pipelines/gradeWorksheet.d.ts +2 -2
  63. package/dist/pipelines/pdfToWorksheet.d.ts +1 -1
  64. package/dist/routers/_app.d.ts +2855 -2051
  65. package/dist/routers/_app.d.ts.map +1 -1
  66. package/dist/routers/_app.js +4 -2
  67. package/dist/routers/_app.js.map +1 -1
  68. package/dist/routers/announcement.d.ts +5 -5
  69. package/dist/routers/announcement.d.ts.map +1 -1
  70. package/dist/routers/announcement.js +25 -5
  71. package/dist/routers/announcement.js.map +1 -1
  72. package/dist/routers/assignment.d.ts +44 -44
  73. package/dist/routers/assignment.d.ts.map +1 -1
  74. package/dist/routers/assignment.js +24 -11
  75. package/dist/routers/assignment.js.map +1 -1
  76. package/dist/routers/class.d.ts +21 -20
  77. package/dist/routers/class.d.ts.map +1 -1
  78. package/dist/routers/class.js +3 -2
  79. package/dist/routers/class.js.map +1 -1
  80. package/dist/routers/comment.d.ts +5 -5
  81. package/dist/routers/conversation.d.ts +6 -6
  82. package/dist/routers/event.d.ts +21 -2
  83. package/dist/routers/event.d.ts.map +1 -1
  84. package/dist/routers/file.d.ts +2 -2
  85. package/dist/routers/folder.d.ts +24 -24
  86. package/dist/routers/labChat.d.ts +7 -7
  87. package/dist/routers/school.d.ts +384 -0
  88. package/dist/routers/school.d.ts.map +1 -0
  89. package/dist/routers/school.js +79 -0
  90. package/dist/routers/school.js.map +1 -0
  91. package/dist/routers/search.d.ts +1 -1
  92. package/dist/routers/worksheet.d.ts +21 -21
  93. package/dist/services/announcement.d.ts.map +1 -1
  94. package/dist/services/announcement.js +10 -6
  95. package/dist/services/announcement.js.map +1 -1
  96. package/dist/services/assignment.d.ts +36 -36
  97. package/dist/services/class.d.ts +20 -19
  98. package/dist/services/class.d.ts.map +1 -1
  99. package/dist/services/class.js +22 -3
  100. package/dist/services/class.js.map +1 -1
  101. package/dist/services/comment.d.ts +5 -5
  102. package/dist/services/comment.d.ts.map +1 -1
  103. package/dist/services/comment.js +8 -3
  104. package/dist/services/comment.js.map +1 -1
  105. package/dist/services/conversation.d.ts +6 -6
  106. package/dist/services/event.d.ts +21 -2
  107. package/dist/services/event.d.ts.map +1 -1
  108. package/dist/services/event.js +11 -7
  109. package/dist/services/event.js.map +1 -1
  110. package/dist/services/file.d.ts +2 -2
  111. package/dist/services/file.d.ts.map +1 -1
  112. package/dist/services/file.js +17 -8
  113. package/dist/services/file.js.map +1 -1
  114. package/dist/services/folder.d.ts +24 -24
  115. package/dist/services/labChat.d.ts +7 -7
  116. package/dist/services/school.d.ts +299 -0
  117. package/dist/services/school.d.ts.map +1 -0
  118. package/dist/services/school.js +225 -0
  119. package/dist/services/school.js.map +1 -0
  120. package/dist/services/worksheet.d.ts +21 -21
  121. package/package.json +1 -1
  122. package/prisma/migrations/20260504071500_school_memberships/migration.sql +81 -0
  123. package/prisma/schema.prisma +66 -2
  124. package/src/models/access.ts +48 -0
  125. package/src/models/announcement.ts +19 -2
  126. package/src/models/assignment.ts +17 -13
  127. package/src/models/attendance.ts +4 -3
  128. package/src/models/auth.ts +8 -9
  129. package/src/models/class.ts +8 -9
  130. package/src/models/comment.ts +29 -40
  131. package/src/models/event.ts +54 -4
  132. package/src/models/file.ts +53 -12
  133. package/src/models/folder.ts +3 -2
  134. package/src/models/labChat.ts +9 -8
  135. package/src/models/school.ts +361 -0
  136. package/src/models/section.ts +3 -2
  137. package/src/models/worksheet.ts +7 -12
  138. package/src/routers/_app.ts +2 -0
  139. package/src/routers/announcement.ts +30 -4
  140. package/src/routers/assignment.ts +27 -9
  141. package/src/routers/class.ts +1 -0
  142. package/src/routers/school.ts +122 -0
  143. package/src/services/announcement.ts +12 -4
  144. package/src/services/class.ts +25 -2
  145. package/src/services/comment.ts +10 -1
  146. package/src/services/event.ts +23 -6
  147. package/src/services/file.ts +20 -6
  148. package/src/services/school.ts +308 -0
@@ -15,9 +15,9 @@ export declare function getWorksheet(worksheetId: string, userId: string): Promi
15
15
  options: import("@prisma/client/runtime/library.js").JsonValue | null;
16
16
  id: string;
17
17
  createdAt: Date;
18
+ updatedAt: Date;
18
19
  order: number | null;
19
20
  points: number;
20
- updatedAt: Date;
21
21
  worksheetId: string;
22
22
  markScheme: import("@prisma/client/runtime/library.js").JsonValue | null;
23
23
  question: string;
@@ -27,8 +27,8 @@ export declare function getWorksheet(worksheetId: string, userId: string): Promi
27
27
  name: string;
28
28
  id: string;
29
29
  createdAt: Date;
30
- classId: string;
31
30
  updatedAt: Date;
31
+ classId: string;
32
32
  }>;
33
33
  /** Check if worksheet exists. */
34
34
  export declare function worksheetExists(id: string): Promise<boolean>;
@@ -40,8 +40,8 @@ export declare function listWorksheets(classId: string): Promise<{
40
40
  name: string;
41
41
  id: string;
42
42
  createdAt: Date;
43
- classId: string;
44
43
  updatedAt: Date;
44
+ classId: string;
45
45
  }[]>;
46
46
  export declare function updateWorksheetRecord(worksheetId: string, userId: string, data: {
47
47
  name?: string;
@@ -49,22 +49,22 @@ export declare function updateWorksheetRecord(worksheetId: string, userId: strin
49
49
  name: string;
50
50
  id: string;
51
51
  createdAt: Date;
52
- classId: string;
53
52
  updatedAt: Date;
53
+ classId: string;
54
54
  }>;
55
55
  export declare function deleteWorksheetRecord(worksheetId: string, userId: string): Promise<{
56
56
  name: string;
57
57
  id: string;
58
58
  createdAt: Date;
59
- classId: string;
60
59
  updatedAt: Date;
60
+ classId: string;
61
61
  }>;
62
62
  export declare function createWorksheetRecord(classId: string, name: string): Promise<{
63
63
  name: string;
64
64
  id: string;
65
65
  createdAt: Date;
66
- classId: string;
67
66
  updatedAt: Date;
67
+ classId: string;
68
68
  }>;
69
69
  /**
70
70
  * Create a worksheet by converting a PDF (URL) into structured questions.
@@ -74,8 +74,8 @@ export declare function createWorksheetFromPdf(classId: string, url: string): Pr
74
74
  name: string;
75
75
  id: string;
76
76
  createdAt: Date;
77
- classId: string;
78
77
  updatedAt: Date;
78
+ classId: string;
79
79
  }>;
80
80
  /**
81
81
  * Create a worksheet from a PDF that was uploaded through the direct-upload flow.
@@ -84,8 +84,8 @@ export declare function createWorksheetFromUploadedPdf(classId: string, fileId:
84
84
  name: string;
85
85
  id: string;
86
86
  createdAt: Date;
87
- classId: string;
88
87
  updatedAt: Date;
88
+ classId: string;
89
89
  }>;
90
90
  export declare function addQuestionToWorksheet(worksheetId: string, userId: string, data: {
91
91
  question: string;
@@ -99,9 +99,9 @@ export declare function addQuestionToWorksheet(worksheetId: string, userId: stri
99
99
  options: import("@prisma/client/runtime/library.js").JsonValue | null;
100
100
  id: string;
101
101
  createdAt: Date;
102
+ updatedAt: Date;
102
103
  order: number | null;
103
104
  points: number;
104
- updatedAt: Date;
105
105
  worksheetId: string;
106
106
  markScheme: import("@prisma/client/runtime/library.js").JsonValue | null;
107
107
  question: string;
@@ -122,9 +122,9 @@ export declare function updateWorksheetQuestionRecord(worksheetId: string, quest
122
122
  options: import("@prisma/client/runtime/library.js").JsonValue | null;
123
123
  id: string;
124
124
  createdAt: Date;
125
+ updatedAt: Date;
125
126
  order: number | null;
126
127
  points: number;
127
- updatedAt: Date;
128
128
  worksheetId: string;
129
129
  markScheme: import("@prisma/client/runtime/library.js").JsonValue | null;
130
130
  question: string;
@@ -135,9 +135,9 @@ export declare function deleteWorksheetQuestionRecord(worksheetId: string, quest
135
135
  options: import("@prisma/client/runtime/library.js").JsonValue | null;
136
136
  id: string;
137
137
  createdAt: Date;
138
+ updatedAt: Date;
138
139
  order: number | null;
139
140
  points: number;
140
- updatedAt: Date;
141
141
  worksheetId: string;
142
142
  markScheme: import("@prisma/client/runtime/library.js").JsonValue | null;
143
143
  question: string;
@@ -152,6 +152,7 @@ export declare function getWorksheetSubmission(worksheetId: string, submissionId
152
152
  status: import(".prisma/client").$Enums.GenerationStatus | null;
153
153
  id: string;
154
154
  createdAt: Date;
155
+ updatedAt: Date | null;
155
156
  studentId: string;
156
157
  questionId: string;
157
158
  response: string;
@@ -159,17 +160,16 @@ export declare function getWorksheetSubmission(worksheetId: string, submissionId
159
160
  markschemeState: import("@prisma/client/runtime/library.js").JsonValue | null;
160
161
  points: number;
161
162
  feedback: string | null;
162
- updatedAt: Date | null;
163
163
  studentWorksheetResponseId: string | null;
164
164
  })[];
165
165
  } & {
166
166
  id: string;
167
- submissionId: string | null;
168
167
  createdAt: Date;
168
+ updatedAt: Date;
169
+ submissionId: string | null;
169
170
  studentId: string;
170
171
  submittedAt: Date | null;
171
172
  submitted: boolean;
172
- updatedAt: Date;
173
173
  worksheetId: string;
174
174
  }>;
175
175
  export declare function answerWorksheetQuestion(worksheetResponseId: string, questionId: string, response: string, userId: string): Promise<{
@@ -177,6 +177,7 @@ export declare function answerWorksheetQuestion(worksheetResponseId: string, que
177
177
  status: import(".prisma/client").$Enums.GenerationStatus | null;
178
178
  id: string;
179
179
  createdAt: Date;
180
+ updatedAt: Date | null;
180
181
  studentId: string;
181
182
  questionId: string;
182
183
  response: string;
@@ -184,23 +185,23 @@ export declare function answerWorksheetQuestion(worksheetResponseId: string, que
184
185
  markschemeState: import("@prisma/client/runtime/library.js").JsonValue | null;
185
186
  points: number;
186
187
  feedback: string | null;
187
- updatedAt: Date | null;
188
188
  studentWorksheetResponseId: string | null;
189
189
  }[];
190
190
  } & {
191
191
  id: string;
192
- submissionId: string | null;
193
192
  createdAt: Date;
193
+ updatedAt: Date;
194
+ submissionId: string | null;
194
195
  studentId: string;
195
196
  submittedAt: Date | null;
196
197
  submitted: boolean;
197
- updatedAt: Date;
198
198
  worksheetId: string;
199
199
  }>;
200
200
  export declare function cancelGrading(worksheetResponseId: string, progressId: string, userId: string): Promise<{
201
201
  status: import(".prisma/client").$Enums.GenerationStatus | null;
202
202
  id: string;
203
203
  createdAt: Date;
204
+ updatedAt: Date | null;
204
205
  studentId: string;
205
206
  questionId: string;
206
207
  response: string;
@@ -208,13 +209,13 @@ export declare function cancelGrading(worksheetResponseId: string, progressId: s
208
209
  markschemeState: import("@prisma/client/runtime/library.js").JsonValue | null;
209
210
  points: number;
210
211
  feedback: string | null;
211
- updatedAt: Date | null;
212
212
  studentWorksheetResponseId: string | null;
213
213
  }>;
214
214
  export declare function regradeQuestion(worksheetResponseId: string, progressId: string, userId: string): Promise<{
215
215
  status: import(".prisma/client").$Enums.GenerationStatus | null;
216
216
  id: string;
217
217
  createdAt: Date;
218
+ updatedAt: Date | null;
218
219
  studentId: string;
219
220
  questionId: string;
220
221
  response: string;
@@ -222,7 +223,6 @@ export declare function regradeQuestion(worksheetResponseId: string, progressId:
222
223
  markschemeState: import("@prisma/client/runtime/library.js").JsonValue | null;
223
224
  points: number;
224
225
  feedback: string | null;
225
- updatedAt: Date | null;
226
226
  studentWorksheetResponseId: string | null;
227
227
  }>;
228
228
  export declare function gradeAnswer(questionId: string, studentWorksheetResponseId: string, userId: string, data: {
@@ -236,6 +236,7 @@ export declare function gradeAnswer(questionId: string, studentWorksheetResponse
236
236
  status: import(".prisma/client").$Enums.GenerationStatus | null;
237
237
  id: string;
238
238
  createdAt: Date;
239
+ updatedAt: Date | null;
239
240
  studentId: string;
240
241
  questionId: string;
241
242
  response: string;
@@ -243,16 +244,15 @@ export declare function gradeAnswer(questionId: string, studentWorksheetResponse
243
244
  markschemeState: import("@prisma/client/runtime/library.js").JsonValue | null;
244
245
  points: number;
245
246
  feedback: string | null;
246
- updatedAt: Date | null;
247
247
  studentWorksheetResponseId: string | null;
248
248
  }>;
249
249
  export declare function addCommentToResponse(responseId: string, comment: string, authorId: string): Promise<{
250
250
  status: import(".prisma/client").$Enums.GenerationStatus | null;
251
251
  id: string;
252
+ createdAt: Date;
252
253
  assignmentId: string | null;
253
254
  submissionId: string | null;
254
255
  announcementId: string | null;
255
- createdAt: Date;
256
256
  modifiedAt: Date | null;
257
257
  content: string;
258
258
  authorId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studious-lms/server",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "description": "Backend server for Studious application",
5
5
  "main": "dist/exportType.js",
6
6
  "types": "dist/exportType.d.ts",
@@ -0,0 +1,81 @@
1
+ -- Add durable multi-school membership and invite support.
2
+
3
+ CREATE TYPE "SchoolMembershipRole" AS ENUM ('ADMIN', 'TEACHER', 'STUDENT');
4
+ CREATE TYPE "SchoolMembershipStatus" AS ENUM ('ACTIVE', 'INVITED', 'SUSPENDED');
5
+ CREATE TYPE "SchoolInviteStatus" AS ENUM ('PENDING', 'ACCEPTED', 'REVOKED', 'EXPIRED');
6
+
7
+ ALTER TABLE "School"
8
+ ALTER COLUMN "logoId" DROP NOT NULL,
9
+ ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
10
+ ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
11
+
12
+ CREATE TABLE "SchoolMembership" (
13
+ "id" TEXT NOT NULL,
14
+ "schoolId" TEXT NOT NULL,
15
+ "userId" TEXT NOT NULL,
16
+ "role" "SchoolMembershipRole" NOT NULL,
17
+ "status" "SchoolMembershipStatus" NOT NULL DEFAULT 'ACTIVE',
18
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
19
+ "updatedAt" TIMESTAMP(3) NOT NULL,
20
+
21
+ CONSTRAINT "SchoolMembership_pkey" PRIMARY KEY ("id")
22
+ );
23
+
24
+ CREATE TABLE "SchoolInvite" (
25
+ "id" TEXT NOT NULL,
26
+ "token" TEXT NOT NULL,
27
+ "email" TEXT NOT NULL,
28
+ "role" "SchoolMembershipRole" NOT NULL,
29
+ "status" "SchoolInviteStatus" NOT NULL DEFAULT 'PENDING',
30
+ "expiresAt" TIMESTAMP(3) NOT NULL,
31
+ "acceptedAt" TIMESTAMP(3),
32
+ "revokedAt" TIMESTAMP(3),
33
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
34
+ "updatedAt" TIMESTAMP(3) NOT NULL,
35
+ "schoolId" TEXT NOT NULL,
36
+ "invitedById" TEXT NOT NULL,
37
+
38
+ CONSTRAINT "SchoolInvite_pkey" PRIMARY KEY ("id")
39
+ );
40
+
41
+ INSERT INTO "SchoolMembership" ("id", "schoolId", "userId", "role", "status", "createdAt", "updatedAt")
42
+ SELECT
43
+ "id",
44
+ "schoolId",
45
+ "id",
46
+ CASE
47
+ WHEN "role" = 'ADMIN' THEN 'ADMIN'::"SchoolMembershipRole"
48
+ WHEN "role" = 'TEACHER' THEN 'TEACHER'::"SchoolMembershipRole"
49
+ ELSE 'STUDENT'::"SchoolMembershipRole"
50
+ END,
51
+ 'ACTIVE'::"SchoolMembershipStatus",
52
+ CURRENT_TIMESTAMP,
53
+ CURRENT_TIMESTAMP
54
+ FROM "User"
55
+ WHERE "schoolId" IS NOT NULL
56
+ ON CONFLICT DO NOTHING;
57
+
58
+ CREATE UNIQUE INDEX "SchoolMembership_schoolId_userId_key" ON "SchoolMembership"("schoolId", "userId");
59
+ CREATE INDEX "SchoolMembership_userId_idx" ON "SchoolMembership"("userId");
60
+ CREATE INDEX "SchoolMembership_schoolId_role_idx" ON "SchoolMembership"("schoolId", "role");
61
+ CREATE INDEX "SchoolMembership_schoolId_status_idx" ON "SchoolMembership"("schoolId", "status");
62
+
63
+ CREATE UNIQUE INDEX "SchoolInvite_token_key" ON "SchoolInvite"("token");
64
+ CREATE INDEX "SchoolInvite_schoolId_status_idx" ON "SchoolInvite"("schoolId", "status");
65
+ CREATE INDEX "SchoolInvite_email_idx" ON "SchoolInvite"("email");
66
+
67
+ ALTER TABLE "SchoolMembership"
68
+ ADD CONSTRAINT "SchoolMembership_schoolId_fkey"
69
+ FOREIGN KEY ("schoolId") REFERENCES "School"("id") ON DELETE CASCADE ON UPDATE CASCADE;
70
+
71
+ ALTER TABLE "SchoolMembership"
72
+ ADD CONSTRAINT "SchoolMembership_userId_fkey"
73
+ FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
74
+
75
+ ALTER TABLE "SchoolInvite"
76
+ ADD CONSTRAINT "SchoolInvite_schoolId_fkey"
77
+ FOREIGN KEY ("schoolId") REFERENCES "School"("id") ON DELETE CASCADE ON UPDATE CASCADE;
78
+
79
+ ALTER TABLE "SchoolInvite"
80
+ ADD CONSTRAINT "SchoolInvite_invitedById_fkey"
81
+ FOREIGN KEY ("invitedById") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -81,15 +81,38 @@ enum SubmissionRecommendationState {
81
81
  DISMISSED
82
82
  }
83
83
 
84
+ enum SchoolMembershipRole {
85
+ ADMIN
86
+ TEACHER
87
+ STUDENT
88
+ }
89
+
90
+ enum SchoolMembershipStatus {
91
+ ACTIVE
92
+ INVITED
93
+ SUSPENDED
94
+ }
95
+
96
+ enum SchoolInviteStatus {
97
+ PENDING
98
+ ACCEPTED
99
+ REVOKED
100
+ EXPIRED
101
+ }
102
+
84
103
  model School {
85
104
  id String @id @default(uuid())
86
105
  name String
87
- logo File @relation(references: [id], fields: [logoId])
88
- logoId String
106
+ logo File? @relation(references: [id], fields: [logoId])
107
+ logoId String?
89
108
  subdomain String? @unique
109
+ createdAt DateTime @default(now())
110
+ updatedAt DateTime @updatedAt
90
111
 
91
112
 
92
113
  users User[]
114
+ memberships SchoolMembership[]
115
+ invites SchoolInvite[]
93
116
  classes Class[]
94
117
  }
95
118
 
@@ -123,6 +146,8 @@ model User {
123
146
 
124
147
  school School? @relation(fields: [schoolId], references: [id])
125
148
  schoolId String?
149
+ schoolMemberships SchoolMembership[]
150
+ sentSchoolInvites SchoolInvite[] @relation("SchoolInviteInvitedBy")
126
151
 
127
152
  // Chat relations
128
153
  conversationMemberships ConversationMember[]
@@ -134,6 +159,45 @@ model User {
134
159
 
135
160
  }
136
161
 
162
+ model SchoolMembership {
163
+ id String @id @default(uuid())
164
+ schoolId String
165
+ userId String
166
+ role SchoolMembershipRole
167
+ status SchoolMembershipStatus @default(ACTIVE)
168
+ createdAt DateTime @default(now())
169
+ updatedAt DateTime @updatedAt
170
+
171
+ school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
172
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
173
+
174
+ @@unique([schoolId, userId])
175
+ @@index([userId])
176
+ @@index([schoolId, role])
177
+ @@index([schoolId, status])
178
+ }
179
+
180
+ model SchoolInvite {
181
+ id String @id @default(uuid())
182
+ token String @unique
183
+ email String
184
+ role SchoolMembershipRole
185
+ status SchoolInviteStatus @default(PENDING)
186
+ expiresAt DateTime
187
+ acceptedAt DateTime?
188
+ revokedAt DateTime?
189
+ createdAt DateTime @default(now())
190
+ updatedAt DateTime @updatedAt
191
+
192
+ schoolId String
193
+ school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
194
+ invitedById String
195
+ invitedBy User @relation("SchoolInviteInvitedBy", fields: [invitedById], references: [id], onDelete: Cascade)
196
+
197
+ @@index([schoolId, status])
198
+ @@index([email])
199
+ }
200
+
137
201
  model UserProfile {
138
202
  id String @id @default(uuid())
139
203
  userId String @unique
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Shared class access filters.
3
+ */
4
+
5
+ export const schoolAdminForClassWhere = (userId: string) => ({
6
+ OR: [
7
+ {
8
+ school: {
9
+ is: {
10
+ memberships: {
11
+ some: {
12
+ userId,
13
+ role: "ADMIN" as const,
14
+ status: "ACTIVE" as const,
15
+ },
16
+ },
17
+ },
18
+ },
19
+ },
20
+ {
21
+ school: {
22
+ is: {
23
+ users: {
24
+ some: {
25
+ id: userId,
26
+ role: "ADMIN" as const,
27
+ },
28
+ },
29
+ },
30
+ },
31
+ },
32
+ ],
33
+ });
34
+
35
+ export const classTeacherOrSchoolAdminWhere = (userId: string) => ({
36
+ OR: [
37
+ { teachers: { some: { id: userId } } },
38
+ schoolAdminForClassWhere(userId),
39
+ ],
40
+ });
41
+
42
+ export const classMemberOrSchoolAdminWhere = (userId: string) => ({
43
+ OR: [
44
+ { teachers: { some: { id: userId } } },
45
+ { students: { some: { id: userId } } },
46
+ schoolAdminForClassWhere(userId),
47
+ ],
48
+ });
@@ -56,13 +56,30 @@ export function findAnnouncementByIdAndClass(id: string, classId: string) {
56
56
  });
57
57
  }
58
58
 
59
- /** @returns Announcement by ID with class and teachers. */
59
+ /** @returns Announcement by ID with class teachers and school admins. */
60
60
  export function findAnnouncementWithClass(id: string) {
61
61
  return prisma.announcement.findUnique({
62
62
  where: { id },
63
63
  include: {
64
64
  class: {
65
- include: { teachers: true },
65
+ include: {
66
+ teachers: true,
67
+ school: {
68
+ include: {
69
+ users: {
70
+ where: { role: "ADMIN" as const },
71
+ select: { id: true },
72
+ },
73
+ memberships: {
74
+ where: {
75
+ role: "ADMIN" as const,
76
+ status: "ACTIVE" as const,
77
+ },
78
+ select: { userId: true },
79
+ },
80
+ },
81
+ },
82
+ },
66
83
  },
67
84
  attachments: {
68
85
  select: {
@@ -2,6 +2,11 @@
2
2
  * Assignment model – assignments, submissions, attachments.
3
3
  */
4
4
  import { prisma } from "../lib/prisma.js";
5
+ import {
6
+ classMemberOrSchoolAdminWhere,
7
+ classTeacherOrSchoolAdminWhere,
8
+ schoolAdminForClassWhere,
9
+ } from "./access.js";
5
10
 
6
11
  const assignmentGetInclude = {
7
12
  submissions: {
@@ -57,7 +62,13 @@ export function findAssignmentWithDetails(id: string) {
57
62
  /** @returns Assignment with attachments for delete. */
58
63
  export function findAssignmentForDelete(id: string, teacherId: string) {
59
64
  return prisma.assignment.findFirst({
60
- where: { id, teacherId },
65
+ where: {
66
+ id,
67
+ OR: [
68
+ { teacherId },
69
+ { class: schoolAdminForClassWhere(teacherId) },
70
+ ],
71
+ },
61
72
  include: {
62
73
  attachments: { include: { thumbnail: true } },
63
74
  submissions: {
@@ -115,12 +126,7 @@ export function findSubmissionByIdForClassMember(submissionId: string, classId:
115
126
  id: submissionId,
116
127
  assignment: {
117
128
  classId,
118
- class: {
119
- OR: [
120
- { teachers: { some: { id: userId } } },
121
- { students: { some: { id: userId } } },
122
- ],
123
- },
129
+ class: classMemberOrSchoolAdminWhere(userId),
124
130
  },
125
131
  },
126
132
  include: {
@@ -148,9 +154,7 @@ export function findSubmissionForUpdate(submissionId: string, userId: string) {
148
154
  { studentId: userId },
149
155
  {
150
156
  assignment: {
151
- class: {
152
- teachers: { some: { id: userId } },
153
- },
157
+ class: classTeacherOrSchoolAdminWhere(userId),
154
158
  },
155
159
  },
156
160
  ],
@@ -204,7 +208,7 @@ export function findAssignmentWithClassForTeacher(assignmentId: string, teacherI
204
208
  return prisma.assignment.findFirst({
205
209
  where: {
206
210
  id: assignmentId,
207
- class: { teachers: { some: { id: teacherId } } },
211
+ class: classTeacherOrSchoolAdminWhere(teacherId),
208
212
  },
209
213
  include: { class: true },
210
214
  });
@@ -216,7 +220,7 @@ export function findSubmissionsByAssignmentForTeacher(assignmentId: string, teac
216
220
  where: {
217
221
  assignment: {
218
222
  id: assignmentId,
219
- class: { teachers: { some: { id: teacherId } } },
223
+ class: classTeacherOrSchoolAdminWhere(teacherId),
220
224
  },
221
225
  },
222
226
  include: {
@@ -251,7 +255,7 @@ export function findSubmissionForTeacherUpdate(submissionId: string, teacherId:
251
255
  where: {
252
256
  id: submissionId,
253
257
  assignment: {
254
- class: { teachers: { some: { id: teacherId } } },
258
+ class: classTeacherOrSchoolAdminWhere(teacherId),
255
259
  },
256
260
  },
257
261
  include: {
@@ -2,6 +2,7 @@
2
2
  * Attendance model – attendance records, present/late/absent students per event.
3
3
  */
4
4
  import { prisma } from "../lib/prisma.js";
5
+ import { classTeacherOrSchoolAdminWhere } from "./access.js";
5
6
 
6
7
  const attendanceInclude = {
7
8
  event: {
@@ -105,12 +106,12 @@ export function findClassWithStudents(classId: string) {
105
106
  });
106
107
  }
107
108
 
108
- /** @returns Class if user is a teacher. */
109
+ /** @returns Class if user is a teacher or school admin. */
109
110
  export function findClassWithTeacher(classId: string, teacherId: string) {
110
- return prisma.class.findUnique({
111
+ return prisma.class.findFirst({
111
112
  where: {
112
113
  id: classId,
113
- teachers: { some: { id: teacherId } },
114
+ ...classTeacherOrSchoolAdminWhere(teacherId),
114
115
  },
115
116
  });
116
117
  }
@@ -5,6 +5,10 @@
5
5
  import { SessionType } from "@prisma/client";
6
6
  import { getRedis } from "../lib/redis.js";
7
7
  import { prisma } from "../lib/prisma.js";
8
+ import {
9
+ classMemberOrSchoolAdminWhere,
10
+ classTeacherOrSchoolAdminWhere,
11
+ } from "./access.js";
8
12
 
9
13
  const AUTH_SESSION_CACHE_PREFIX = "auth-session:";
10
14
 
@@ -246,9 +250,7 @@ export async function findUserBySessionToken(token: string) {
246
250
  /** @returns Class IDs where user is a teacher. */
247
251
  export function findTeacherClassesByUserId(userId: string) {
248
252
  return prisma.class.findMany({
249
- where: {
250
- teachers: { some: { id: userId } },
251
- },
253
+ where: classTeacherOrSchoolAdminWhere(userId),
252
254
  select: { id: true },
253
255
  });
254
256
  }
@@ -258,20 +260,17 @@ export function findClassWithMember(classId: string, userId: string) {
258
260
  return prisma.class.findFirst({
259
261
  where: {
260
262
  id: classId,
261
- OR: [
262
- { students: { some: { id: userId } } },
263
- { teachers: { some: { id: userId } } },
264
- ],
263
+ ...classMemberOrSchoolAdminWhere(userId),
265
264
  },
266
265
  });
267
266
  }
268
267
 
269
- /** @returns Class if user is a teacher. */
268
+ /** @returns Class if user is a teacher or school admin. */
270
269
  export function findClassWithTeacher(classId: string, teacherId: string) {
271
270
  return prisma.class.findFirst({
272
271
  where: {
273
272
  id: classId,
274
- teachers: { some: { id: teacherId } },
273
+ ...classTeacherOrSchoolAdminWhere(teacherId),
275
274
  },
276
275
  });
277
276
  }
@@ -10,6 +10,10 @@ import type { PrismaClient, WorksheetQuestion } from "@prisma/client";
10
10
  import { prisma } from "../lib/prisma.js";
11
11
  import { v4 as uuidv4 } from 'uuid';
12
12
  import { copyFile } from "../lib/googleCloudStorage.js";
13
+ import {
14
+ classMemberOrSchoolAdminWhere,
15
+ classTeacherOrSchoolAdminWhere,
16
+ } from "./access.js";
13
17
 
14
18
  const classIncludeForGetAll = {
15
19
  assignments: {
@@ -55,7 +59,7 @@ const classIncludeForGetAll = {
55
59
  /** @returns Classes where user is a teacher. */
56
60
  export function findTeacherClasses(userId: string) {
57
61
  return prisma.class.findMany({
58
- where: { teachers: { some: { id: userId } } },
62
+ where: classTeacherOrSchoolAdminWhere(userId),
59
63
  include: classIncludeForGetAll,
60
64
  });
61
65
  }
@@ -68,12 +72,12 @@ export function findStudentClasses(userId: string) {
68
72
  });
69
73
  }
70
74
 
71
- /** @returns Class if user is a teacher. */
75
+ /** @returns Class if user is a teacher or school admin. */
72
76
  export function isTeacherInClass(classId: string, userId: string) {
73
77
  return prisma.class.findFirst({
74
78
  where: {
75
79
  id: classId,
76
- teachers: { some: { id: userId } },
80
+ ...classTeacherOrSchoolAdminWhere(userId),
77
81
  },
78
82
  });
79
83
  }
@@ -218,12 +222,7 @@ export function findClassWithAssignments(
218
222
  where: {
219
223
  id: classId,
220
224
  ...(options.userId
221
- ? {
222
- OR: [
223
- { students: { some: { id: options.userId } } },
224
- { teachers: { some: { id: options.userId } } },
225
- ],
226
- }
225
+ ? classMemberOrSchoolAdminWhere(options.userId)
227
226
  : {}),
228
227
  },
229
228
  include: {