@studious-lms/server 1.7.0 → 1.9.0

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 (212) hide show
  1. package/dist/middleware/security.d.ts.map +1 -1
  2. package/dist/middleware/security.js +3 -3
  3. package/dist/middleware/security.js.map +1 -1
  4. package/dist/models/access.d.ts +21 -66
  5. package/dist/models/access.d.ts.map +1 -1
  6. package/dist/models/access.js +10 -26
  7. package/dist/models/access.js.map +1 -1
  8. package/dist/models/agenda.d.ts +4 -2
  9. package/dist/models/agenda.d.ts.map +1 -1
  10. package/dist/models/announcement.d.ts +4 -3
  11. package/dist/models/announcement.d.ts.map +1 -1
  12. package/dist/models/announcement.js +2 -3
  13. package/dist/models/announcement.js.map +1 -1
  14. package/dist/models/assignment.d.ts +40 -33
  15. package/dist/models/assignment.d.ts.map +1 -1
  16. package/dist/models/auth.d.ts +2 -4
  17. package/dist/models/auth.d.ts.map +1 -1
  18. package/dist/models/auth.js +2 -3
  19. package/dist/models/auth.js.map +1 -1
  20. package/dist/models/class.d.ts +34 -6
  21. package/dist/models/class.d.ts.map +1 -1
  22. package/dist/models/class.js +15 -2
  23. package/dist/models/class.js.map +1 -1
  24. package/dist/models/cloudStorage.d.ts +153 -0
  25. package/dist/models/cloudStorage.d.ts.map +1 -0
  26. package/dist/models/cloudStorage.js +295 -0
  27. package/dist/models/cloudStorage.js.map +1 -0
  28. package/dist/models/comment.d.ts +0 -24
  29. package/dist/models/comment.d.ts.map +1 -1
  30. package/dist/models/comment.js +2 -6
  31. package/dist/models/comment.js.map +1 -1
  32. package/dist/models/conversation.d.ts +22 -22
  33. package/dist/models/department.d.ts +154 -0
  34. package/dist/models/department.d.ts.map +1 -0
  35. package/dist/models/department.js +135 -0
  36. package/dist/models/department.js.map +1 -0
  37. package/dist/models/event.d.ts +10 -4
  38. package/dist/models/event.d.ts.map +1 -1
  39. package/dist/models/event.js +2 -5
  40. package/dist/models/event.js.map +1 -1
  41. package/dist/models/file.d.ts +52 -36
  42. package/dist/models/file.d.ts.map +1 -1
  43. package/dist/models/file.js +4 -42
  44. package/dist/models/file.js.map +1 -1
  45. package/dist/models/folder.d.ts +47 -0
  46. package/dist/models/folder.d.ts.map +1 -1
  47. package/dist/models/labChat.d.ts +11 -9
  48. package/dist/models/labChat.d.ts.map +1 -1
  49. package/dist/models/message.d.ts +46 -12
  50. package/dist/models/message.d.ts.map +1 -1
  51. package/dist/models/message.js +19 -10
  52. package/dist/models/message.js.map +1 -1
  53. package/dist/models/newtonChat.d.ts +2 -1
  54. package/dist/models/newtonChat.d.ts.map +1 -1
  55. package/dist/models/school.d.ts +45 -12
  56. package/dist/models/school.d.ts.map +1 -1
  57. package/dist/models/school.js +19 -2
  58. package/dist/models/school.js.map +1 -1
  59. package/dist/models/section.d.ts +2 -1
  60. package/dist/models/section.d.ts.map +1 -1
  61. package/dist/models/tag.d.ts +117 -0
  62. package/dist/models/tag.d.ts.map +1 -0
  63. package/dist/models/tag.js +113 -0
  64. package/dist/models/tag.js.map +1 -0
  65. package/dist/models/worksheet.d.ts +3 -0
  66. package/dist/models/worksheet.d.ts.map +1 -1
  67. package/dist/pipelines/aiLabChatContract.d.ts +8 -8
  68. package/dist/routers/_app.d.ts +6704 -5222
  69. package/dist/routers/_app.d.ts.map +1 -1
  70. package/dist/routers/_app.js +8 -2
  71. package/dist/routers/_app.js.map +1 -1
  72. package/dist/routers/agenda.d.ts +4 -2
  73. package/dist/routers/agenda.d.ts.map +1 -1
  74. package/dist/routers/announcement.d.ts.map +1 -1
  75. package/dist/routers/announcement.js +3 -5
  76. package/dist/routers/announcement.js.map +1 -1
  77. package/dist/routers/assignment.d.ts +49 -40
  78. package/dist/routers/assignment.d.ts.map +1 -1
  79. package/dist/routers/assignment.js +2 -3
  80. package/dist/routers/assignment.js.map +1 -1
  81. package/dist/routers/class.d.ts +92 -19
  82. package/dist/routers/class.d.ts.map +1 -1
  83. package/dist/routers/class.js +5 -2
  84. package/dist/routers/class.js.map +1 -1
  85. package/dist/routers/conversation.d.ts +5 -5
  86. package/dist/routers/department.d.ts +293 -0
  87. package/dist/routers/department.d.ts.map +1 -0
  88. package/dist/routers/department.js +93 -0
  89. package/dist/routers/department.js.map +1 -0
  90. package/dist/routers/event.d.ts +4 -3
  91. package/dist/routers/event.d.ts.map +1 -1
  92. package/dist/routers/file.d.ts +10 -8
  93. package/dist/routers/file.d.ts.map +1 -1
  94. package/dist/routers/file.js +26 -17
  95. package/dist/routers/file.js.map +1 -1
  96. package/dist/routers/folder.d.ts +163 -186
  97. package/dist/routers/folder.d.ts.map +1 -1
  98. package/dist/routers/folder.js +102 -88
  99. package/dist/routers/folder.js.map +1 -1
  100. package/dist/routers/labChat.d.ts +13 -4
  101. package/dist/routers/labChat.d.ts.map +1 -1
  102. package/dist/routers/message.d.ts +20 -2
  103. package/dist/routers/message.d.ts.map +1 -1
  104. package/dist/routers/newtonChat.d.ts +10 -1
  105. package/dist/routers/newtonChat.d.ts.map +1 -1
  106. package/dist/routers/permissions.d.ts +120 -0
  107. package/dist/routers/permissions.d.ts.map +1 -0
  108. package/dist/routers/permissions.js +50 -0
  109. package/dist/routers/permissions.js.map +1 -0
  110. package/dist/routers/school.d.ts +160 -17
  111. package/dist/routers/school.d.ts.map +1 -1
  112. package/dist/routers/school.js +46 -3
  113. package/dist/routers/school.js.map +1 -1
  114. package/dist/routers/tag.d.ts +119 -0
  115. package/dist/routers/tag.d.ts.map +1 -0
  116. package/dist/routers/tag.js +52 -0
  117. package/dist/routers/tag.js.map +1 -0
  118. package/dist/routers/worksheet.d.ts +1 -0
  119. package/dist/routers/worksheet.d.ts.map +1 -1
  120. package/dist/seedDatabase.d.ts +0 -1
  121. package/dist/seedDatabase.d.ts.map +1 -1
  122. package/dist/services/agenda.d.ts +4 -2
  123. package/dist/services/agenda.d.ts.map +1 -1
  124. package/dist/services/assignment.d.ts +49 -40
  125. package/dist/services/assignment.d.ts.map +1 -1
  126. package/dist/services/class.d.ts +92 -19
  127. package/dist/services/class.d.ts.map +1 -1
  128. package/dist/services/class.js +92 -5
  129. package/dist/services/class.js.map +1 -1
  130. package/dist/services/conversation.d.ts +5 -5
  131. package/dist/services/department.d.ts +200 -0
  132. package/dist/services/department.d.ts.map +1 -0
  133. package/dist/services/department.js +368 -0
  134. package/dist/services/department.js.map +1 -0
  135. package/dist/services/event.d.ts +4 -3
  136. package/dist/services/event.d.ts.map +1 -1
  137. package/dist/services/file.d.ts +105 -3
  138. package/dist/services/file.d.ts.map +1 -1
  139. package/dist/services/file.js +167 -41
  140. package/dist/services/file.js.map +1 -1
  141. package/dist/services/folder.d.ts +220 -149
  142. package/dist/services/folder.d.ts.map +1 -1
  143. package/dist/services/folder.js +326 -196
  144. package/dist/services/folder.js.map +1 -1
  145. package/dist/services/labChat.d.ts +13 -4
  146. package/dist/services/labChat.d.ts.map +1 -1
  147. package/dist/services/labChat.js +4 -3
  148. package/dist/services/labChat.js.map +1 -1
  149. package/dist/services/message.d.ts +20 -2
  150. package/dist/services/message.d.ts.map +1 -1
  151. package/dist/services/message.js +7 -23
  152. package/dist/services/message.js.map +1 -1
  153. package/dist/services/newtonChat.d.ts +9 -0
  154. package/dist/services/newtonChat.d.ts.map +1 -1
  155. package/dist/services/newtonChat.js +4 -3
  156. package/dist/services/newtonChat.js.map +1 -1
  157. package/dist/services/permissions.d.ts +188 -0
  158. package/dist/services/permissions.d.ts.map +1 -0
  159. package/dist/services/permissions.js +661 -0
  160. package/dist/services/permissions.js.map +1 -0
  161. package/dist/services/school.d.ts +142 -15
  162. package/dist/services/school.d.ts.map +1 -1
  163. package/dist/services/school.js +308 -54
  164. package/dist/services/school.js.map +1 -1
  165. package/dist/services/tag.d.ts +71 -0
  166. package/dist/services/tag.d.ts.map +1 -0
  167. package/dist/services/tag.js +178 -0
  168. package/dist/services/tag.js.map +1 -0
  169. package/dist/services/worksheet.d.ts +1 -0
  170. package/dist/services/worksheet.d.ts.map +1 -1
  171. package/dist/utils/aiUser.d.ts.map +1 -1
  172. package/dist/utils/aiUser.js +2 -3
  173. package/dist/utils/aiUser.js.map +1 -1
  174. package/package.json +1 -1
  175. package/prisma/migrations/20260509220000_institution_overhaul/migration.sql +184 -0
  176. package/prisma/migrations/20260510170000_add_folder_permissions/migration.sql +93 -0
  177. package/prisma/schema.prisma +193 -35
  178. package/src/middleware/security.ts +1 -1
  179. package/src/models/access.ts +8 -24
  180. package/src/models/announcement.ts +0 -1
  181. package/src/models/auth.ts +0 -1
  182. package/src/models/class.ts +13 -0
  183. package/src/models/cloudStorage.ts +327 -0
  184. package/src/models/comment.ts +0 -4
  185. package/src/models/department.ts +159 -0
  186. package/src/models/event.ts +0 -3
  187. package/src/models/file.ts +2 -40
  188. package/src/models/message.ts +19 -5
  189. package/src/models/school.ts +26 -0
  190. package/src/models/tag.ts +140 -0
  191. package/src/routers/_app.ts +6 -0
  192. package/src/routers/announcement.ts +1 -3
  193. package/src/routers/assignment.ts +0 -1
  194. package/src/routers/class.ts +3 -0
  195. package/src/routers/department.ts +174 -0
  196. package/src/routers/file.ts +29 -26
  197. package/src/routers/folder.ts +127 -118
  198. package/src/routers/permissions.ts +71 -0
  199. package/src/routers/school.ts +83 -0
  200. package/src/routers/tag.ts +78 -0
  201. package/src/services/class.ts +109 -4
  202. package/src/services/department.ts +478 -0
  203. package/src/services/file.ts +172 -43
  204. package/src/services/folder.ts +399 -237
  205. package/src/services/labChat.ts +2 -1
  206. package/src/services/message.ts +5 -21
  207. package/src/services/newtonChat.ts +2 -1
  208. package/src/services/permissions.ts +778 -0
  209. package/src/services/school.ts +369 -57
  210. package/src/services/tag.ts +229 -0
  211. package/src/utils/aiUser.ts +0 -1
  212. package/tests/unit/services/permissions.test.ts +463 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Tag service — institution-scoped tag CRUD and assignment to taggable
3
+ * targets (users, classes, rubrics, boundaries).
4
+ */
5
+
6
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="6ab21e9e-024c-5347-9864-92a8edf76dbe")}catch(e){}}();
7
+ import { TRPCError } from "@trpc/server";
8
+ import { prisma } from "../lib/prisma.js";
9
+ import { attachTagToTarget, createTag, deleteTag, detachTagFromTarget, getTag, listTagsAttachedTo, listTagsForSchool, updateTag, } from "../models/tag.js";
10
+ import { findActiveSchoolAdminMembership, findSchoolMembership, } from "../models/school.js";
11
+ async function requireAdmin(schoolId, userId) {
12
+ const membership = await findActiveSchoolAdminMembership(schoolId, userId);
13
+ if (!membership) {
14
+ throw new TRPCError({
15
+ code: "FORBIDDEN",
16
+ message: "Only school admins can manage tags",
17
+ });
18
+ }
19
+ }
20
+ async function requireMember(schoolId, userId) {
21
+ const membership = await findSchoolMembership(schoolId, userId);
22
+ if (!membership || membership.status !== "ACTIVE") {
23
+ throw new TRPCError({
24
+ code: "FORBIDDEN",
25
+ message: "You aren't a member of this institution",
26
+ });
27
+ }
28
+ return membership;
29
+ }
30
+ export async function listTags(userId, input) {
31
+ await requireMember(input.schoolId, userId);
32
+ return listTagsForSchool(input.schoolId, {
33
+ departmentId: input.departmentId,
34
+ });
35
+ }
36
+ export async function createTagRecord(userId, input) {
37
+ await requireAdmin(input.schoolId, userId);
38
+ if (input.departmentId) {
39
+ const dept = await prisma.department.findUnique({
40
+ where: { id: input.departmentId },
41
+ select: { schoolId: true },
42
+ });
43
+ if (!dept || dept.schoolId !== input.schoolId) {
44
+ throw new TRPCError({
45
+ code: "BAD_REQUEST",
46
+ message: "Department does not belong to this institution",
47
+ });
48
+ }
49
+ }
50
+ return createTag(input);
51
+ }
52
+ export async function updateTagRecord(userId, input) {
53
+ const tag = await getTag(input.tagId);
54
+ if (!tag) {
55
+ throw new TRPCError({ code: "NOT_FOUND", message: "Tag not found" });
56
+ }
57
+ await requireAdmin(tag.schoolId, userId);
58
+ if (input.departmentId) {
59
+ const dept = await prisma.department.findUnique({
60
+ where: { id: input.departmentId },
61
+ select: { schoolId: true },
62
+ });
63
+ if (!dept || dept.schoolId !== tag.schoolId) {
64
+ throw new TRPCError({
65
+ code: "BAD_REQUEST",
66
+ message: "Department does not belong to this institution",
67
+ });
68
+ }
69
+ }
70
+ return updateTag(input.tagId, {
71
+ name: input.name,
72
+ color: input.color,
73
+ departmentId: input.departmentId,
74
+ });
75
+ }
76
+ export async function deleteTagRecord(userId, tagId) {
77
+ const tag = await getTag(tagId);
78
+ if (!tag) {
79
+ throw new TRPCError({ code: "NOT_FOUND", message: "Tag not found" });
80
+ }
81
+ await requireAdmin(tag.schoolId, userId);
82
+ await deleteTag(tagId);
83
+ return { success: true };
84
+ }
85
+ async function ensureTargetInScope(schoolId, target, targetId) {
86
+ switch (target) {
87
+ case "USER": {
88
+ const inSchool = await prisma.schoolMembership.findFirst({
89
+ where: { schoolId, userId: targetId, status: "ACTIVE" },
90
+ select: { id: true },
91
+ });
92
+ if (!inSchool) {
93
+ throw new TRPCError({
94
+ code: "BAD_REQUEST",
95
+ message: "User is not in this institution",
96
+ });
97
+ }
98
+ return;
99
+ }
100
+ case "CLASS": {
101
+ const inSchool = await prisma.class.findFirst({
102
+ where: { id: targetId, schoolId },
103
+ select: { id: true },
104
+ });
105
+ if (!inSchool) {
106
+ throw new TRPCError({
107
+ code: "BAD_REQUEST",
108
+ message: "Class is not in this institution",
109
+ });
110
+ }
111
+ return;
112
+ }
113
+ case "RUBRIC": {
114
+ const inSchool = await prisma.markScheme.findFirst({
115
+ where: {
116
+ id: targetId,
117
+ OR: [
118
+ { class: { schoolId } },
119
+ { department: { schoolId } },
120
+ ],
121
+ },
122
+ select: { id: true },
123
+ });
124
+ if (!inSchool) {
125
+ throw new TRPCError({
126
+ code: "BAD_REQUEST",
127
+ message: "Rubric is not in this institution",
128
+ });
129
+ }
130
+ return;
131
+ }
132
+ case "BOUNDARY": {
133
+ const inSchool = await prisma.gradingBoundary.findFirst({
134
+ where: {
135
+ id: targetId,
136
+ OR: [
137
+ { class: { schoolId } },
138
+ { department: { schoolId } },
139
+ ],
140
+ },
141
+ select: { id: true },
142
+ });
143
+ if (!inSchool) {
144
+ throw new TRPCError({
145
+ code: "BAD_REQUEST",
146
+ message: "Boundary is not in this institution",
147
+ });
148
+ }
149
+ return;
150
+ }
151
+ }
152
+ }
153
+ export async function attachTag(userId, input) {
154
+ const tag = await getTag(input.tagId);
155
+ if (!tag) {
156
+ throw new TRPCError({ code: "NOT_FOUND", message: "Tag not found" });
157
+ }
158
+ await requireMember(tag.schoolId, userId);
159
+ await ensureTargetInScope(tag.schoolId, input.target, input.targetId);
160
+ return attachTagToTarget(input);
161
+ }
162
+ export async function detachTag(userId, input) {
163
+ const tag = await getTag(input.tagId);
164
+ if (!tag) {
165
+ throw new TRPCError({ code: "NOT_FOUND", message: "Tag not found" });
166
+ }
167
+ await requireMember(tag.schoolId, userId);
168
+ return detachTagFromTarget(input);
169
+ }
170
+ export async function getAttachedTags(userId, input) {
171
+ const tags = await listTagsAttachedTo(input.target, input.targetId);
172
+ if (tags[0]) {
173
+ await requireMember(tags[0].schoolId, userId);
174
+ }
175
+ return tags;
176
+ }
177
+ //# sourceMappingURL=tag.js.map
178
+ //# debugId=6ab21e9e-024c-5347-9864-92a8edf76dbe
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag.js","sources":["services/tag.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Tag service — institution-scoped tag CRUD and assignment to taggable\n * targets (users, classes, rubrics, boundaries).\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { prisma } from \"../lib/prisma.js\";\nimport {\n attachTagToTarget,\n createTag,\n deleteTag,\n detachTagFromTarget,\n getTag,\n listTagsAttachedTo,\n listTagsForSchool,\n type TagTarget,\n updateTag,\n} from \"../models/tag.js\";\nimport {\n findActiveSchoolAdminMembership,\n findSchoolMembership,\n} from \"../models/school.js\";\n\nasync function requireAdmin(schoolId: string, userId: string) {\n const membership = await findActiveSchoolAdminMembership(schoolId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only school admins can manage tags\",\n });\n }\n}\n\nasync function requireMember(schoolId: string, userId: string) {\n const membership = await findSchoolMembership(schoolId, userId);\n if (!membership || membership.status !== \"ACTIVE\") {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You aren't a member of this institution\",\n });\n }\n return membership;\n}\n\nexport async function listTags(\n userId: string,\n input: { schoolId: string; departmentId?: string | null }\n) {\n await requireMember(input.schoolId, userId);\n return listTagsForSchool(input.schoolId, {\n departmentId: input.departmentId,\n });\n}\n\nexport async function createTagRecord(\n userId: string,\n input: {\n schoolId: string;\n departmentId?: string | null;\n name: string;\n color?: string;\n }\n) {\n await requireAdmin(input.schoolId, userId);\n if (input.departmentId) {\n const dept = await prisma.department.findUnique({\n where: { id: input.departmentId },\n select: { schoolId: true },\n });\n if (!dept || dept.schoolId !== input.schoolId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Department does not belong to this institution\",\n });\n }\n }\n return createTag(input);\n}\n\nexport async function updateTagRecord(\n userId: string,\n input: {\n tagId: string;\n name?: string;\n color?: string;\n departmentId?: string | null;\n }\n) {\n const tag = await getTag(input.tagId);\n if (!tag) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Tag not found\" });\n }\n await requireAdmin(tag.schoolId, userId);\n if (input.departmentId) {\n const dept = await prisma.department.findUnique({\n where: { id: input.departmentId },\n select: { schoolId: true },\n });\n if (!dept || dept.schoolId !== tag.schoolId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Department does not belong to this institution\",\n });\n }\n }\n return updateTag(input.tagId, {\n name: input.name,\n color: input.color,\n departmentId: input.departmentId,\n });\n}\n\nexport async function deleteTagRecord(userId: string, tagId: string) {\n const tag = await getTag(tagId);\n if (!tag) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Tag not found\" });\n }\n await requireAdmin(tag.schoolId, userId);\n await deleteTag(tagId);\n return { success: true };\n}\n\nasync function ensureTargetInScope(\n schoolId: string,\n target: TagTarget,\n targetId: string\n) {\n switch (target) {\n case \"USER\": {\n const inSchool = await prisma.schoolMembership.findFirst({\n where: { schoolId, userId: targetId, status: \"ACTIVE\" },\n select: { id: true },\n });\n if (!inSchool) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"User is not in this institution\",\n });\n }\n return;\n }\n case \"CLASS\": {\n const inSchool = await prisma.class.findFirst({\n where: { id: targetId, schoolId },\n select: { id: true },\n });\n if (!inSchool) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Class is not in this institution\",\n });\n }\n return;\n }\n case \"RUBRIC\": {\n const inSchool = await prisma.markScheme.findFirst({\n where: {\n id: targetId,\n OR: [\n { class: { schoolId } },\n { department: { schoolId } },\n ],\n },\n select: { id: true },\n });\n if (!inSchool) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Rubric is not in this institution\",\n });\n }\n return;\n }\n case \"BOUNDARY\": {\n const inSchool = await prisma.gradingBoundary.findFirst({\n where: {\n id: targetId,\n OR: [\n { class: { schoolId } },\n { department: { schoolId } },\n ],\n },\n select: { id: true },\n });\n if (!inSchool) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Boundary is not in this institution\",\n });\n }\n return;\n }\n }\n}\n\nexport async function attachTag(\n userId: string,\n input: { tagId: string; target: TagTarget; targetId: string }\n) {\n const tag = await getTag(input.tagId);\n if (!tag) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Tag not found\" });\n }\n await requireMember(tag.schoolId, userId);\n await ensureTargetInScope(tag.schoolId, input.target, input.targetId);\n return attachTagToTarget(input);\n}\n\nexport async function detachTag(\n userId: string,\n input: { tagId: string; target: TagTarget; targetId: string }\n) {\n const tag = await getTag(input.tagId);\n if (!tag) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Tag not found\" });\n }\n await requireMember(tag.schoolId, userId);\n return detachTagFromTarget(input);\n}\n\nexport async function getAttachedTags(\n userId: string,\n input: { target: TagTarget; targetId: string }\n) {\n const tags = await listTagsAttachedTo(input.target, input.targetId);\n if (tags[0]) {\n await requireMember(tags[0].schoolId, userId);\n }\n return tags;\n}\n"],"names":[],"mappings":"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","debug_id":"6ab21e9e-024c-5347-9864-92a8edf76dbe"}
@@ -9,6 +9,7 @@ export declare function getWorksheet(worksheetId: string, userId: string): Promi
9
9
  color: string | null;
10
10
  section: string;
11
11
  syllabus: string | null;
12
+ departmentId: string | null;
12
13
  } | null;
13
14
  questions: {
14
15
  type: import(".prisma/client").$Enums.WorksheetQuestionType;
@@ -1 +1 @@
1
- {"version":3,"file":"worksheet.d.ts","sourceRoot":"/","sources":["services/worksheet.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"worksheet.d.ts","sourceRoot":"/","sources":["services/worksheet.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"aiUser.d.ts","sourceRoot":"/","sources":["utils/aiUser.ts"],"names":[],"mappings":"AAQA,uDAAuD;AACvD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0CxD;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEhD"}
1
+ {"version":3,"file":"aiUser.d.ts","sourceRoot":"/","sources":["utils/aiUser.ts"],"names":[],"mappings":"AAQA,uDAAuD;AACvD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAyCxD;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEhD"}
@@ -2,7 +2,7 @@
2
2
  * AI user util – ensures AI assistant user exists, provides ID and isAIUser check.
3
3
  */
4
4
 
5
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0ed01933-9d5a-548d-b86b-94f607dd41ed")}catch(e){}}();
5
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3221c7f8-7f11-5e45-9148-3a8ddd53a1f0")}catch(e){}}();
6
6
  import { prisma } from '../lib/prisma.js';
7
7
  import { logger } from './logger.js';
8
8
  const AI_USER_ID = 'AI_ASSISTANT';
@@ -24,7 +24,6 @@ export async function ensureAIUserExists() {
24
24
  email: 'ai@studious-lms.com',
25
25
  password: 'ai-system-user', // Not used for login
26
26
  verified: true,
27
- role: 'NONE', // Special role for AI
28
27
  profile: {
29
28
  create: {
30
29
  displayName: 'AI Assistant',
@@ -59,4 +58,4 @@ export function isAIUser(userId) {
59
58
  return userId === AI_USER_ID;
60
59
  }
61
60
  //# sourceMappingURL=aiUser.js.map
62
- //# debugId=0ed01933-9d5a-548d-b86b-94f607dd41ed
61
+ //# debugId=3221c7f8-7f11-5e45-9148-3a8ddd53a1f0
@@ -1 +1 @@
1
- {"version":3,"file":"aiUser.js","sources":["utils/aiUser.ts"],"sourceRoot":"/","sourcesContent":["/**\n * AI user util – ensures AI assistant user exists, provides ID and isAIUser check.\n */\nimport { prisma } from '../lib/prisma.js';\nimport { logger } from './logger.js';\n\nconst AI_USER_ID = 'AI_ASSISTANT';\n\n/** Ensure AI assistant user exists in the database. */\nexport async function ensureAIUserExists(): Promise<void> {\n try {\n // Check if AI user already exists\n const existingAIUser = await prisma.user.findUnique({\n where: { id: AI_USER_ID },\n });\n\n if (existingAIUser) {\n return; // AI user already exists\n }\n\n // Create AI user\n await prisma.user.create({\n data: {\n id: AI_USER_ID,\n username: 'ai-assistant',\n email: 'ai@studious-lms.com',\n password: 'ai-system-user', // Not used for login\n verified: true,\n role: 'NONE', // Special role for AI\n profile: {\n create: {\n displayName: 'AI Assistant',\n bio: 'Intelligent assistant for lab chats and educational support',\n profilePicture: null,\n },\n },\n },\n });\n\n logger.info('AI user created successfully', { userId: AI_USER_ID });\n\n } catch (error) {\n // If user already exists (race condition), that's okay\n if (error instanceof Error && error.message.includes('unique constraint')) {\n logger.info('AI user already exists (race condition handled)');\n return;\n }\n\n logger.error('Failed to create AI user', { error });\n throw error;\n }\n}\n\n/**\n * Get the AI user ID\n */\nexport function getAIUserId(): string {\n return AI_USER_ID;\n}\n\n/**\n * Check if a user ID belongs to the AI assistant\n */\nexport function isAIUser(userId: string): boolean {\n return userId === AI_USER_ID;\n}\n"],"names":[],"mappings":"AAAA;;GAEG;;;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,UAAU,GAAG,cAAc,CAAC;AAElC,uDAAuD;AACvD,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,IAAI,CAAC;QACH,kCAAkC;QAClC,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;YAClD,KAAK,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE;SAC1B,CAAC,CAAC;QAEH,IAAI,cAAc,EAAE,CAAC;YACnB,OAAO,CAAC,yBAAyB;QACnC,CAAC;QAED,iBAAiB;QACjB,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;YACvB,IAAI,EAAE;gBACJ,EAAE,EAAE,UAAU;gBACd,QAAQ,EAAE,cAAc;gBACxB,KAAK,EAAE,qBAAqB;gBAC5B,QAAQ,EAAE,gBAAgB,EAAE,qBAAqB;gBACjD,QAAQ,EAAE,IAAI;gBACd,IAAI,EAAE,MAAM,EAAE,sBAAsB;gBACpC,OAAO,EAAE;oBACP,MAAM,EAAE;wBACN,WAAW,EAAE,cAAc;wBAC3B,GAAG,EAAE,6DAA6D;wBAClE,cAAc,EAAE,IAAI;qBACrB;iBACF;aACF;SACF,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,8BAA8B,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IAEtE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,uDAAuD;QACvD,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;YAC1E,MAAM,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;YAC/D,OAAO;QACT,CAAC;QAED,MAAM,CAAC,KAAK,CAAC,0BAA0B,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACpD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAc;IACrC,OAAO,MAAM,KAAK,UAAU,CAAC;AAC/B,CAAC","debug_id":"0ed01933-9d5a-548d-b86b-94f607dd41ed"}
1
+ {"version":3,"file":"aiUser.js","sources":["utils/aiUser.ts"],"sourceRoot":"/","sourcesContent":["/**\n * AI user util – ensures AI assistant user exists, provides ID and isAIUser check.\n */\nimport { prisma } from '../lib/prisma.js';\nimport { logger } from './logger.js';\n\nconst AI_USER_ID = 'AI_ASSISTANT';\n\n/** Ensure AI assistant user exists in the database. */\nexport async function ensureAIUserExists(): Promise<void> {\n try {\n // Check if AI user already exists\n const existingAIUser = await prisma.user.findUnique({\n where: { id: AI_USER_ID },\n });\n\n if (existingAIUser) {\n return; // AI user already exists\n }\n\n // Create AI user\n await prisma.user.create({\n data: {\n id: AI_USER_ID,\n username: 'ai-assistant',\n email: 'ai@studious-lms.com',\n password: 'ai-system-user', // Not used for login\n verified: true,\n profile: {\n create: {\n displayName: 'AI Assistant',\n bio: 'Intelligent assistant for lab chats and educational support',\n profilePicture: null,\n },\n },\n },\n });\n\n logger.info('AI user created successfully', { userId: AI_USER_ID });\n\n } catch (error) {\n // If user already exists (race condition), that's okay\n if (error instanceof Error && error.message.includes('unique constraint')) {\n logger.info('AI user already exists (race condition handled)');\n return;\n }\n\n logger.error('Failed to create AI user', { error });\n throw error;\n }\n}\n\n/**\n * Get the AI user ID\n */\nexport function getAIUserId(): string {\n return AI_USER_ID;\n}\n\n/**\n * Check if a user ID belongs to the AI assistant\n */\nexport function isAIUser(userId: string): boolean {\n return userId === AI_USER_ID;\n}\n"],"names":[],"mappings":"AAAA;;GAEG;;;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,UAAU,GAAG,cAAc,CAAC;AAElC,uDAAuD;AACvD,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,IAAI,CAAC;QACH,kCAAkC;QAClC,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;YAClD,KAAK,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE;SAC1B,CAAC,CAAC;QAEH,IAAI,cAAc,EAAE,CAAC;YACnB,OAAO,CAAC,yBAAyB;QACnC,CAAC;QAED,iBAAiB;QACjB,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;YACvB,IAAI,EAAE;gBACJ,EAAE,EAAE,UAAU;gBACd,QAAQ,EAAE,cAAc;gBACxB,KAAK,EAAE,qBAAqB;gBAC5B,QAAQ,EAAE,gBAAgB,EAAE,qBAAqB;gBACjD,QAAQ,EAAE,IAAI;gBACd,OAAO,EAAE;oBACP,MAAM,EAAE;wBACN,WAAW,EAAE,cAAc;wBAC3B,GAAG,EAAE,6DAA6D;wBAClE,cAAc,EAAE,IAAI;qBACrB;iBACF;aACF;SACF,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,8BAA8B,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IAEtE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,uDAAuD;QACvD,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;YAC1E,MAAM,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;YAC/D,OAAO;QACT,CAAC;QAED,MAAM,CAAC,KAAK,CAAC,0BAA0B,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACpD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAc;IACrC,OAAO,MAAM,KAAK,UAAU,CAAC;AAC/B,CAAC","debug_id":"3221c7f8-7f11-5e45-9148-3a8ddd53a1f0"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studious-lms/server",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "Backend server for Studious application",
5
5
  "main": "dist/exportType.js",
6
6
  "types": "dist/exportType.d.ts",
@@ -0,0 +1,184 @@
1
+ -- Institution overhaul: departments, tags, cloud storage, class affiliation,
2
+ -- and template grading artefacts.
3
+
4
+ CREATE TYPE "TagTarget" AS ENUM ('USER', 'CLASS', 'RUBRIC', 'BOUNDARY');
5
+ CREATE TYPE "FolderScope" AS ENUM ('CLASS', 'SCHOOL_STORAGE', 'PERSONAL', 'GENERIC');
6
+
7
+ -- School: optional description.
8
+ ALTER TABLE "School"
9
+ ADD COLUMN "description" TEXT;
10
+
11
+ -- Folder: extend with scope + alternate root owners (school, personal user).
12
+ ALTER TABLE "Folder"
13
+ ADD COLUMN "scope" "FolderScope" NOT NULL DEFAULT 'GENERIC',
14
+ ADD COLUMN "schoolStorageId" TEXT,
15
+ ADD COLUMN "personalOwnerId" TEXT;
16
+
17
+ -- Backfill existing class root folders to CLASS scope so the data layer can
18
+ -- distinguish roots from generic subfolders.
19
+ UPDATE "Folder"
20
+ SET "scope" = 'CLASS'
21
+ WHERE "classId" IS NOT NULL AND "parentFolderId" IS NULL;
22
+
23
+ CREATE UNIQUE INDEX "Folder_schoolStorageId_key" ON "Folder"("schoolStorageId");
24
+ CREATE UNIQUE INDEX "Folder_personalOwnerId_key" ON "Folder"("personalOwnerId");
25
+
26
+ ALTER TABLE "Folder"
27
+ ADD CONSTRAINT "Folder_schoolStorageId_fkey"
28
+ FOREIGN KEY ("schoolStorageId") REFERENCES "School"("id")
29
+ ON DELETE SET NULL ON UPDATE CASCADE,
30
+ ADD CONSTRAINT "Folder_personalOwnerId_fkey"
31
+ FOREIGN KEY ("personalOwnerId") REFERENCES "User"("id")
32
+ ON DELETE CASCADE ON UPDATE CASCADE;
33
+
34
+ -- Department.
35
+ CREATE TABLE "Department" (
36
+ "id" TEXT NOT NULL,
37
+ "schoolId" TEXT NOT NULL,
38
+ "name" TEXT NOT NULL,
39
+ "description" TEXT,
40
+ "color" TEXT DEFAULT '#3B82F6',
41
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
42
+ "updatedAt" TIMESTAMP(3) NOT NULL,
43
+
44
+ CONSTRAINT "Department_pkey" PRIMARY KEY ("id")
45
+ );
46
+
47
+ CREATE UNIQUE INDEX "Department_schoolId_name_key" ON "Department"("schoolId", "name");
48
+ CREATE INDEX "Department_schoolId_idx" ON "Department"("schoolId");
49
+
50
+ ALTER TABLE "Department"
51
+ ADD CONSTRAINT "Department_schoolId_fkey"
52
+ FOREIGN KEY ("schoolId") REFERENCES "School"("id")
53
+ ON DELETE CASCADE ON UPDATE CASCADE;
54
+
55
+ -- User <-> Department (M:N).
56
+ CREATE TABLE "_UserDepartments" (
57
+ "A" TEXT NOT NULL,
58
+ "B" TEXT NOT NULL
59
+ );
60
+ CREATE UNIQUE INDEX "_UserDepartments_AB_unique" ON "_UserDepartments"("A", "B");
61
+ CREATE INDEX "_UserDepartments_B_index" ON "_UserDepartments"("B");
62
+ ALTER TABLE "_UserDepartments"
63
+ ADD CONSTRAINT "_UserDepartments_A_fkey"
64
+ FOREIGN KEY ("A") REFERENCES "Department"("id") ON DELETE CASCADE ON UPDATE CASCADE,
65
+ ADD CONSTRAINT "_UserDepartments_B_fkey"
66
+ FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
67
+
68
+ -- Tag.
69
+ CREATE TABLE "Tag" (
70
+ "id" TEXT NOT NULL,
71
+ "schoolId" TEXT NOT NULL,
72
+ "departmentId" TEXT,
73
+ "name" TEXT NOT NULL,
74
+ "color" TEXT NOT NULL DEFAULT '#3B82F6',
75
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
76
+ "updatedAt" TIMESTAMP(3) NOT NULL,
77
+
78
+ CONSTRAINT "Tag_pkey" PRIMARY KEY ("id")
79
+ );
80
+
81
+ CREATE UNIQUE INDEX "Tag_schoolId_name_key" ON "Tag"("schoolId", "name");
82
+ CREATE INDEX "Tag_schoolId_idx" ON "Tag"("schoolId");
83
+ CREATE INDEX "Tag_departmentId_idx" ON "Tag"("departmentId");
84
+
85
+ ALTER TABLE "Tag"
86
+ ADD CONSTRAINT "Tag_schoolId_fkey"
87
+ FOREIGN KEY ("schoolId") REFERENCES "School"("id")
88
+ ON DELETE CASCADE ON UPDATE CASCADE,
89
+ ADD CONSTRAINT "Tag_departmentId_fkey"
90
+ FOREIGN KEY ("departmentId") REFERENCES "Department"("id")
91
+ ON DELETE SET NULL ON UPDATE CASCADE;
92
+
93
+ -- Tag join tables.
94
+ CREATE TABLE "_UserTags" (
95
+ "A" TEXT NOT NULL,
96
+ "B" TEXT NOT NULL
97
+ );
98
+ CREATE UNIQUE INDEX "_UserTags_AB_unique" ON "_UserTags"("A", "B");
99
+ CREATE INDEX "_UserTags_B_index" ON "_UserTags"("B");
100
+ ALTER TABLE "_UserTags"
101
+ ADD CONSTRAINT "_UserTags_A_fkey" FOREIGN KEY ("A") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE,
102
+ ADD CONSTRAINT "_UserTags_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
103
+
104
+ CREATE TABLE "_ClassTags" (
105
+ "A" TEXT NOT NULL,
106
+ "B" TEXT NOT NULL
107
+ );
108
+ CREATE UNIQUE INDEX "_ClassTags_AB_unique" ON "_ClassTags"("A", "B");
109
+ CREATE INDEX "_ClassTags_B_index" ON "_ClassTags"("B");
110
+ ALTER TABLE "_ClassTags"
111
+ ADD CONSTRAINT "_ClassTags_A_fkey" FOREIGN KEY ("A") REFERENCES "Class"("id") ON DELETE CASCADE ON UPDATE CASCADE,
112
+ ADD CONSTRAINT "_ClassTags_B_fkey" FOREIGN KEY ("B") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
113
+
114
+ CREATE TABLE "_MarkSchemeTags" (
115
+ "A" TEXT NOT NULL,
116
+ "B" TEXT NOT NULL
117
+ );
118
+ CREATE UNIQUE INDEX "_MarkSchemeTags_AB_unique" ON "_MarkSchemeTags"("A", "B");
119
+ CREATE INDEX "_MarkSchemeTags_B_index" ON "_MarkSchemeTags"("B");
120
+ ALTER TABLE "_MarkSchemeTags"
121
+ ADD CONSTRAINT "_MarkSchemeTags_A_fkey" FOREIGN KEY ("A") REFERENCES "MarkScheme"("id") ON DELETE CASCADE ON UPDATE CASCADE,
122
+ ADD CONSTRAINT "_MarkSchemeTags_B_fkey" FOREIGN KEY ("B") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
123
+
124
+ CREATE TABLE "_GradingBoundaryTags" (
125
+ "A" TEXT NOT NULL,
126
+ "B" TEXT NOT NULL
127
+ );
128
+ CREATE UNIQUE INDEX "_GradingBoundaryTags_AB_unique" ON "_GradingBoundaryTags"("A", "B");
129
+ CREATE INDEX "_GradingBoundaryTags_B_index" ON "_GradingBoundaryTags"("B");
130
+ ALTER TABLE "_GradingBoundaryTags"
131
+ ADD CONSTRAINT "_GradingBoundaryTags_A_fkey" FOREIGN KEY ("A") REFERENCES "GradingBoundary"("id") ON DELETE CASCADE ON UPDATE CASCADE,
132
+ ADD CONSTRAINT "_GradingBoundaryTags_B_fkey" FOREIGN KEY ("B") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
133
+
134
+ -- Class: department + tags relations (tags are via _ClassTags above).
135
+ ALTER TABLE "Class"
136
+ ADD COLUMN "departmentId" TEXT;
137
+
138
+ CREATE INDEX "Class_schoolId_idx" ON "Class"("schoolId");
139
+ CREATE INDEX "Class_departmentId_idx" ON "Class"("departmentId");
140
+
141
+ ALTER TABLE "Class"
142
+ ADD CONSTRAINT "Class_departmentId_fkey"
143
+ FOREIGN KEY ("departmentId") REFERENCES "Department"("id")
144
+ ON DELETE SET NULL ON UPDATE CASCADE;
145
+
146
+ -- MarkScheme: optional class, departmentId, name.
147
+ ALTER TABLE "MarkScheme"
148
+ DROP CONSTRAINT IF EXISTS "MarkScheme_classId_fkey";
149
+
150
+ ALTER TABLE "MarkScheme"
151
+ ALTER COLUMN "classId" DROP NOT NULL,
152
+ ADD COLUMN "departmentId" TEXT,
153
+ ADD COLUMN "name" TEXT;
154
+
155
+ ALTER TABLE "MarkScheme"
156
+ ADD CONSTRAINT "MarkScheme_classId_fkey"
157
+ FOREIGN KEY ("classId") REFERENCES "Class"("id")
158
+ ON DELETE CASCADE ON UPDATE CASCADE,
159
+ ADD CONSTRAINT "MarkScheme_departmentId_fkey"
160
+ FOREIGN KEY ("departmentId") REFERENCES "Department"("id")
161
+ ON DELETE CASCADE ON UPDATE CASCADE;
162
+
163
+ CREATE INDEX "MarkScheme_classId_idx" ON "MarkScheme"("classId");
164
+ CREATE INDEX "MarkScheme_departmentId_idx" ON "MarkScheme"("departmentId");
165
+
166
+ -- GradingBoundary: optional class, departmentId, name.
167
+ ALTER TABLE "GradingBoundary"
168
+ DROP CONSTRAINT IF EXISTS "GradingBoundary_classId_fkey";
169
+
170
+ ALTER TABLE "GradingBoundary"
171
+ ALTER COLUMN "classId" DROP NOT NULL,
172
+ ADD COLUMN "departmentId" TEXT,
173
+ ADD COLUMN "name" TEXT;
174
+
175
+ ALTER TABLE "GradingBoundary"
176
+ ADD CONSTRAINT "GradingBoundary_classId_fkey"
177
+ FOREIGN KEY ("classId") REFERENCES "Class"("id")
178
+ ON DELETE CASCADE ON UPDATE CASCADE,
179
+ ADD CONSTRAINT "GradingBoundary_departmentId_fkey"
180
+ FOREIGN KEY ("departmentId") REFERENCES "Department"("id")
181
+ ON DELETE CASCADE ON UPDATE CASCADE;
182
+
183
+ CREATE INDEX "GradingBoundary_classId_idx" ON "GradingBoundary"("classId");
184
+ CREATE INDEX "GradingBoundary_departmentId_idx" ON "GradingBoundary"("departmentId");
@@ -0,0 +1,93 @@
1
+ -- Add folder-level ACL permissions (v1: folder-only).
2
+ --
3
+ -- This migration introduces the Permission table and its supporting enums.
4
+ -- It is *purely additive* — no existing column or row is modified, so it can
5
+ -- be applied safely on a live database without backfill.
6
+ --
7
+ -- File-level grants are deliberately deferred. When/if added later, they can
8
+ -- be a non-breaking additive migration (new columns + back-relation).
9
+
10
+ -- CreateEnum
11
+ CREATE TYPE "PermissionPrincipalType" AS ENUM (
12
+ 'USER',
13
+ 'TAG',
14
+ 'CLASS',
15
+ 'CLASS_TEACHERS',
16
+ 'CLASS_STUDENTS',
17
+ 'DEPARTMENT',
18
+ 'SCHOOL'
19
+ );
20
+
21
+ -- CreateEnum
22
+ CREATE TYPE "PermissionLevel" AS ENUM ('READ', 'WRITE');
23
+
24
+ -- CreateTable
25
+ CREATE TABLE "Permission" (
26
+ "id" TEXT NOT NULL DEFAULT gen_random_uuid()::text,
27
+ "folderId" TEXT NOT NULL,
28
+ "principalType" "PermissionPrincipalType" NOT NULL,
29
+ "userId" TEXT,
30
+ "tagId" TEXT,
31
+ "classId" TEXT,
32
+ "departmentId" TEXT,
33
+ "schoolId" TEXT,
34
+ "level" "PermissionLevel" NOT NULL,
35
+ "grantedById" TEXT NOT NULL,
36
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
37
+
38
+ CONSTRAINT "Permission_pkey" PRIMARY KEY ("id")
39
+ );
40
+
41
+ -- CreateIndex
42
+ CREATE INDEX "Permission_folderId_idx" ON "Permission"("folderId");
43
+
44
+ -- CreateIndex
45
+ CREATE INDEX "Permission_userId_idx" ON "Permission"("userId");
46
+
47
+ -- CreateIndex
48
+ CREATE INDEX "Permission_tagId_idx" ON "Permission"("tagId");
49
+
50
+ -- CreateIndex
51
+ CREATE INDEX "Permission_classId_idx" ON "Permission"("classId");
52
+
53
+ -- CreateIndex
54
+ CREATE INDEX "Permission_departmentId_idx" ON "Permission"("departmentId");
55
+
56
+ -- CreateIndex
57
+ CREATE INDEX "Permission_schoolId_idx" ON "Permission"("schoolId");
58
+
59
+ -- CreateIndex
60
+ -- One grant per (folder, principal). Re-granting upserts the level.
61
+ -- Postgres treats nulls as distinct in unique indexes, but the service layer
62
+ -- guarantees exactly one principal id is non-null per row, so this still
63
+ -- uniquely identifies the row.
64
+ CREATE UNIQUE INDEX "Permission_folderId_principalType_userId_tagId_classId_depa_key"
65
+ ON "Permission"("folderId", "principalType", "userId", "tagId", "classId", "departmentId", "schoolId");
66
+
67
+ -- AddForeignKey
68
+ ALTER TABLE "Permission" ADD CONSTRAINT "Permission_folderId_fkey"
69
+ FOREIGN KEY ("folderId") REFERENCES "Folder"("id") ON DELETE CASCADE ON UPDATE CASCADE;
70
+
71
+ -- AddForeignKey
72
+ ALTER TABLE "Permission" ADD CONSTRAINT "Permission_userId_fkey"
73
+ FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
74
+
75
+ -- AddForeignKey
76
+ ALTER TABLE "Permission" ADD CONSTRAINT "Permission_tagId_fkey"
77
+ FOREIGN KEY ("tagId") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
78
+
79
+ -- AddForeignKey
80
+ ALTER TABLE "Permission" ADD CONSTRAINT "Permission_classId_fkey"
81
+ FOREIGN KEY ("classId") REFERENCES "Class"("id") ON DELETE CASCADE ON UPDATE CASCADE;
82
+
83
+ -- AddForeignKey
84
+ ALTER TABLE "Permission" ADD CONSTRAINT "Permission_departmentId_fkey"
85
+ FOREIGN KEY ("departmentId") REFERENCES "Department"("id") ON DELETE CASCADE ON UPDATE CASCADE;
86
+
87
+ -- AddForeignKey
88
+ ALTER TABLE "Permission" ADD CONSTRAINT "Permission_schoolId_fkey"
89
+ FOREIGN KEY ("schoolId") REFERENCES "School"("id") ON DELETE CASCADE ON UPDATE CASCADE;
90
+
91
+ -- AddForeignKey
92
+ ALTER TABLE "Permission" ADD CONSTRAINT "Permission_grantedById_fkey"
93
+ FOREIGN KEY ("grantedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;