@studious-lms/server 1.27.0 → 1.28.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 (106) hide show
  1. package/dist/index.d.ts.map +1 -1
  2. package/dist/index.js +31 -4
  3. package/dist/index.js.map +1 -1
  4. package/dist/instrument.js +9 -4
  5. package/dist/instrument.js.map +1 -1
  6. package/dist/lib/fileUpload.d.ts +15 -0
  7. package/dist/lib/fileUpload.d.ts.map +1 -1
  8. package/dist/lib/fileUpload.js +43 -2
  9. package/dist/lib/fileUpload.js.map +1 -1
  10. package/dist/pipelines/aiLabChatContract.d.ts +2 -2
  11. package/dist/pipelines/generateVisualization.d.ts +33 -0
  12. package/dist/pipelines/generateVisualization.d.ts.map +1 -0
  13. package/dist/pipelines/generateVisualization.js +79 -0
  14. package/dist/pipelines/generateVisualization.js.map +1 -0
  15. package/dist/routers/_app.d.ts +891 -53
  16. package/dist/routers/_app.d.ts.map +1 -1
  17. package/dist/routers/_app.js +4 -2
  18. package/dist/routers/_app.js.map +1 -1
  19. package/dist/routers/admin.d.ts +297 -0
  20. package/dist/routers/admin.d.ts.map +1 -1
  21. package/dist/routers/admin.js +43 -2
  22. package/dist/routers/admin.js.map +1 -1
  23. package/dist/routers/class.d.ts.map +1 -1
  24. package/dist/routers/class.js +16 -3
  25. package/dist/routers/class.js.map +1 -1
  26. package/dist/routers/module.d.ts +1 -1
  27. package/dist/routers/visualization.d.ts +124 -0
  28. package/dist/routers/visualization.d.ts.map +1 -0
  29. package/dist/routers/visualization.js +41 -0
  30. package/dist/routers/visualization.js.map +1 -0
  31. package/dist/services/adminClass.d.ts +153 -0
  32. package/dist/services/adminClass.d.ts.map +1 -0
  33. package/dist/services/adminClass.js +196 -0
  34. package/dist/services/adminClass.js.map +1 -0
  35. package/dist/services/adminInstitution.d.ts +2 -0
  36. package/dist/services/adminInstitution.d.ts.map +1 -1
  37. package/dist/services/adminInstitution.js +11 -4
  38. package/dist/services/adminInstitution.js.map +1 -1
  39. package/dist/services/adminMetrics.d.ts +8 -0
  40. package/dist/services/adminMetrics.d.ts.map +1 -1
  41. package/dist/services/adminMetrics.js +30 -3
  42. package/dist/services/adminMetrics.js.map +1 -1
  43. package/dist/services/adminSpam.d.ts +76 -0
  44. package/dist/services/adminSpam.d.ts.map +1 -0
  45. package/dist/services/adminSpam.js +163 -0
  46. package/dist/services/adminSpam.js.map +1 -0
  47. package/dist/services/adminSql.d.ts +36 -0
  48. package/dist/services/adminSql.d.ts.map +1 -0
  49. package/dist/services/adminSql.js +172 -0
  50. package/dist/services/adminSql.js.map +1 -0
  51. package/dist/services/adminTelemetry.d.ts +56 -0
  52. package/dist/services/adminTelemetry.d.ts.map +1 -0
  53. package/dist/services/adminTelemetry.js +147 -0
  54. package/dist/services/adminTelemetry.js.map +1 -0
  55. package/dist/services/adminUser.d.ts +45 -0
  56. package/dist/services/adminUser.d.ts.map +1 -1
  57. package/dist/services/adminUser.js +48 -11
  58. package/dist/services/adminUser.js.map +1 -1
  59. package/dist/services/auth.d.ts.map +1 -1
  60. package/dist/services/auth.js +10 -4
  61. package/dist/services/auth.js.map +1 -1
  62. package/dist/services/fees.d.ts.map +1 -1
  63. package/dist/services/fees.js +7 -3
  64. package/dist/services/fees.js.map +1 -1
  65. package/dist/services/module.d.ts.map +1 -1
  66. package/dist/services/module.js +23 -4
  67. package/dist/services/module.js.map +1 -1
  68. package/dist/services/visualization.d.ts +72 -0
  69. package/dist/services/visualization.d.ts.map +1 -0
  70. package/dist/services/visualization.js +122 -0
  71. package/dist/services/visualization.js.map +1 -0
  72. package/dist/utils/sentryTrpc.d.ts +3 -0
  73. package/dist/utils/sentryTrpc.d.ts.map +1 -0
  74. package/dist/utils/sentryTrpc.js +26 -0
  75. package/dist/utils/sentryTrpc.js.map +1 -0
  76. package/package.json +1 -1
  77. package/prisma/migrations/20261025120000_add_visualizations/migration.sql +27 -0
  78. package/prisma/schema.prisma +24 -0
  79. package/src/index.ts +30 -4
  80. package/src/instrument.ts +8 -2
  81. package/src/lib/fileUpload.ts +49 -0
  82. package/src/pipelines/generateVisualization.ts +107 -0
  83. package/src/routers/_app.ts +2 -0
  84. package/src/routers/admin.ts +72 -0
  85. package/src/routers/class.ts +14 -1
  86. package/src/routers/visualization.ts +60 -0
  87. package/src/services/adminClass.ts +222 -0
  88. package/src/services/adminInstitution.ts +9 -2
  89. package/src/services/adminMetrics.ts +35 -0
  90. package/src/services/adminSpam.ts +224 -0
  91. package/src/services/adminSql.ts +214 -0
  92. package/src/services/adminTelemetry.ts +194 -0
  93. package/src/services/adminUser.ts +61 -9
  94. package/src/services/auth.ts +8 -2
  95. package/src/services/fees.ts +5 -1
  96. package/src/services/module.ts +29 -2
  97. package/src/services/visualization.ts +147 -0
  98. package/src/utils/sentryTrpc.ts +25 -0
  99. package/tests/unit/lib/fileUploadSession.test.ts +67 -0
  100. package/tests/unit/services/adminClass.test.ts +102 -0
  101. package/tests/unit/services/adminSpam.test.ts +108 -0
  102. package/tests/unit/services/adminSql.test.ts +56 -0
  103. package/tests/unit/services/adminTelemetry.test.ts +47 -0
  104. package/tests/unit/services/adminUser.test.ts +2 -0
  105. package/tests/unit/services/visualization.test.ts +130 -0
  106. package/tests/unit/utils/sentryTrpc.test.ts +15 -0
@@ -0,0 +1,196 @@
1
+
2
+ !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]="952402d2-ba9b-50c7-b9c7-3bb46a71d8a6")}catch(e){}}();
3
+ import { TRPCError } from '@trpc/server';
4
+ import { prisma } from '../lib/prisma.js';
5
+ import { classifyUser } from './adminSpam.js';
6
+ export async function listClassesForAdmin(input) {
7
+ const skip = input.page * input.pageSize;
8
+ const filters = [];
9
+ const q = input.query?.trim();
10
+ if (q) {
11
+ filters.push({
12
+ OR: [
13
+ { name: { contains: q, mode: 'insensitive' } },
14
+ { subject: { contains: q, mode: 'insensitive' } },
15
+ { section: { contains: q, mode: 'insensitive' } },
16
+ ],
17
+ });
18
+ }
19
+ if (input.schoolId)
20
+ filters.push({ schoolId: input.schoolId });
21
+ if (input.membership === 'populated')
22
+ filters.push({ students: { some: {} } });
23
+ if (input.membership === 'empty')
24
+ filters.push({ students: { none: {} } });
25
+ const where = filters.length ? { AND: filters } : {};
26
+ const orderBy = input.sort === 'students'
27
+ ? [{ students: { _count: 'desc' } }, { name: 'asc' }]
28
+ : input.sort === 'assignments'
29
+ ? [{ assignments: { _count: 'desc' } }, { name: 'asc' }]
30
+ : [{ name: 'asc' }];
31
+ const [items, total] = await Promise.all([
32
+ prisma.class.findMany({
33
+ where,
34
+ orderBy,
35
+ skip,
36
+ take: input.pageSize,
37
+ select: {
38
+ id: true,
39
+ name: true,
40
+ subject: true,
41
+ section: true,
42
+ school: { select: { id: true, name: true } },
43
+ department: { select: { id: true, name: true } },
44
+ teachers: { select: { id: true, username: true }, take: 3 },
45
+ assignments: {
46
+ orderBy: { createdAt: 'desc' },
47
+ take: 1,
48
+ select: { createdAt: true },
49
+ },
50
+ _count: {
51
+ select: {
52
+ students: true,
53
+ teachers: true,
54
+ assignments: true,
55
+ announcements: true,
56
+ discussions: true,
57
+ worksheets: true,
58
+ events: true,
59
+ modules: true,
60
+ },
61
+ },
62
+ },
63
+ }),
64
+ prisma.class.count({ where }),
65
+ ]);
66
+ return {
67
+ items: items.map(({ assignments, ...rest }) => ({
68
+ ...rest,
69
+ lastAssignmentAt: assignments[0]?.createdAt ?? null,
70
+ })),
71
+ total,
72
+ page: input.page,
73
+ pageSize: input.pageSize,
74
+ pageCount: Math.max(1, Math.ceil(total / input.pageSize)),
75
+ };
76
+ }
77
+ const MEMBER_SELECT = {
78
+ id: true,
79
+ username: true,
80
+ email: true,
81
+ verified: true,
82
+ _count: { select: { teacherIn: true, studentIn: true, submissions: true } },
83
+ };
84
+ export async function getClassForAdmin(id) {
85
+ const cls = await prisma.class.findUnique({
86
+ where: { id },
87
+ select: {
88
+ id: true,
89
+ name: true,
90
+ subject: true,
91
+ section: true,
92
+ color: true,
93
+ school: { select: { id: true, name: true } },
94
+ department: { select: { id: true, name: true } },
95
+ teachers: { orderBy: { username: 'asc' }, select: MEMBER_SELECT },
96
+ students: {
97
+ orderBy: { username: 'asc' },
98
+ take: 200,
99
+ select: MEMBER_SELECT,
100
+ },
101
+ assignments: {
102
+ orderBy: { createdAt: 'desc' },
103
+ take: 20,
104
+ select: {
105
+ id: true,
106
+ title: true,
107
+ type: true,
108
+ graded: true,
109
+ dueDate: true,
110
+ createdAt: true,
111
+ teacher: { select: { id: true, username: true } },
112
+ _count: { select: { submissions: true } },
113
+ },
114
+ },
115
+ announcements: {
116
+ orderBy: { createdAt: 'desc' },
117
+ take: 10,
118
+ select: {
119
+ id: true,
120
+ createdAt: true,
121
+ remarks: true,
122
+ teacher: { select: { id: true, username: true } },
123
+ },
124
+ },
125
+ _count: {
126
+ select: {
127
+ students: true,
128
+ teachers: true,
129
+ assignments: true,
130
+ announcements: true,
131
+ discussions: true,
132
+ worksheets: true,
133
+ events: true,
134
+ modules: true,
135
+ sections: true,
136
+ },
137
+ },
138
+ },
139
+ });
140
+ if (!cls) {
141
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Class not found' });
142
+ }
143
+ // Per-student submission counts scoped to this class, so a roster row shows
144
+ // engagement here rather than platform-wide totals.
145
+ const submissionsByStudent = await prisma.submission.groupBy({
146
+ by: ['studentId'],
147
+ where: { assignment: { classId: id } },
148
+ _count: { _all: true },
149
+ });
150
+ const submissionCount = new Map(submissionsByStudent.map((row) => [row.studentId, row._count._all]));
151
+ const withSignals = (user) => ({
152
+ ...user,
153
+ signals: classifyUser(user),
154
+ submissionsInClass: submissionCount.get(user.id) ?? 0,
155
+ });
156
+ return {
157
+ ...cls,
158
+ teachers: cls.teachers.map(withSignals),
159
+ students: cls.students.map(withSignals),
160
+ announcements: cls.announcements.map((announcement) => ({
161
+ ...announcement,
162
+ remarks: announcement.remarks.length > 160
163
+ ? `${announcement.remarks.slice(0, 160)}…`
164
+ : announcement.remarks,
165
+ })),
166
+ suspectedMembers: [...cls.teachers, ...cls.students].filter((user) => classifyUser(user).suspected).length,
167
+ };
168
+ }
169
+ /** Classes a single user teaches or studies in, for the user detail page. */
170
+ export async function listClassesForUserForAdmin(userId) {
171
+ const select = {
172
+ id: true,
173
+ name: true,
174
+ subject: true,
175
+ section: true,
176
+ school: { select: { id: true, name: true } },
177
+ _count: { select: { students: true, assignments: true } },
178
+ };
179
+ const [teaching, enrolled] = await Promise.all([
180
+ prisma.class.findMany({
181
+ where: { teachers: { some: { id: userId } } },
182
+ orderBy: { name: 'asc' },
183
+ take: 100,
184
+ select,
185
+ }),
186
+ prisma.class.findMany({
187
+ where: { students: { some: { id: userId } } },
188
+ orderBy: { name: 'asc' },
189
+ take: 100,
190
+ select,
191
+ }),
192
+ ]);
193
+ return { teaching, enrolled };
194
+ }
195
+ //# sourceMappingURL=adminClass.js.map
196
+ //# debugId=952402d2-ba9b-50c7-b9c7-3bb46a71d8a6
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adminClass.js","sources":["services/adminClass.ts"],"sourceRoot":"/","sourcesContent":["import type { Prisma } from '@prisma/client';\nimport { TRPCError } from '@trpc/server';\nimport { prisma } from '../lib/prisma.js';\nimport { classifyUser } from './adminSpam.js';\n\nexport type AdminClassMembership = 'all' | 'populated' | 'empty';\nexport type AdminClassSort = 'name' | 'students' | 'assignments';\n\nexport async function listClassesForAdmin(input: {\n page: number;\n pageSize: number;\n query?: string;\n schoolId?: string;\n membership?: AdminClassMembership;\n sort?: AdminClassSort;\n}) {\n const skip = input.page * input.pageSize;\n const filters: Prisma.ClassWhereInput[] = [];\n\n const q = input.query?.trim();\n if (q) {\n filters.push({\n OR: [\n { name: { contains: q, mode: 'insensitive' } },\n { subject: { contains: q, mode: 'insensitive' } },\n { section: { contains: q, mode: 'insensitive' } },\n ],\n });\n }\n\n if (input.schoolId) filters.push({ schoolId: input.schoolId });\n if (input.membership === 'populated') filters.push({ students: { some: {} } });\n if (input.membership === 'empty') filters.push({ students: { none: {} } });\n\n const where: Prisma.ClassWhereInput = filters.length ? { AND: filters } : {};\n\n const orderBy: Prisma.ClassOrderByWithRelationInput[] =\n input.sort === 'students'\n ? [{ students: { _count: 'desc' } }, { name: 'asc' }]\n : input.sort === 'assignments'\n ? [{ assignments: { _count: 'desc' } }, { name: 'asc' }]\n : [{ name: 'asc' }];\n\n const [items, total] = await Promise.all([\n prisma.class.findMany({\n where,\n orderBy,\n skip,\n take: input.pageSize,\n select: {\n id: true,\n name: true,\n subject: true,\n section: true,\n school: { select: { id: true, name: true } },\n department: { select: { id: true, name: true } },\n teachers: { select: { id: true, username: true }, take: 3 },\n assignments: {\n orderBy: { createdAt: 'desc' },\n take: 1,\n select: { createdAt: true },\n },\n _count: {\n select: {\n students: true,\n teachers: true,\n assignments: true,\n announcements: true,\n discussions: true,\n worksheets: true,\n events: true,\n modules: true,\n },\n },\n },\n }),\n prisma.class.count({ where }),\n ]);\n\n return {\n items: items.map(({ assignments, ...rest }) => ({\n ...rest,\n lastAssignmentAt: assignments[0]?.createdAt ?? null,\n })),\n total,\n page: input.page,\n pageSize: input.pageSize,\n pageCount: Math.max(1, Math.ceil(total / input.pageSize)),\n };\n}\n\nconst MEMBER_SELECT = {\n id: true,\n username: true,\n email: true,\n verified: true,\n _count: { select: { teacherIn: true, studentIn: true, submissions: true } },\n} satisfies Prisma.UserSelect;\n\nexport async function getClassForAdmin(id: string) {\n const cls = await prisma.class.findUnique({\n where: { id },\n select: {\n id: true,\n name: true,\n subject: true,\n section: true,\n color: true,\n school: { select: { id: true, name: true } },\n department: { select: { id: true, name: true } },\n teachers: { orderBy: { username: 'asc' }, select: MEMBER_SELECT },\n students: {\n orderBy: { username: 'asc' },\n take: 200,\n select: MEMBER_SELECT,\n },\n assignments: {\n orderBy: { createdAt: 'desc' },\n take: 20,\n select: {\n id: true,\n title: true,\n type: true,\n graded: true,\n dueDate: true,\n createdAt: true,\n teacher: { select: { id: true, username: true } },\n _count: { select: { submissions: true } },\n },\n },\n announcements: {\n orderBy: { createdAt: 'desc' },\n take: 10,\n select: {\n id: true,\n createdAt: true,\n remarks: true,\n teacher: { select: { id: true, username: true } },\n },\n },\n _count: {\n select: {\n students: true,\n teachers: true,\n assignments: true,\n announcements: true,\n discussions: true,\n worksheets: true,\n events: true,\n modules: true,\n sections: true,\n },\n },\n },\n });\n\n if (!cls) {\n throw new TRPCError({ code: 'NOT_FOUND', message: 'Class not found' });\n }\n\n // Per-student submission counts scoped to this class, so a roster row shows\n // engagement here rather than platform-wide totals.\n const submissionsByStudent = await prisma.submission.groupBy({\n by: ['studentId'],\n where: { assignment: { classId: id } },\n _count: { _all: true },\n });\n const submissionCount = new Map(\n submissionsByStudent.map((row) => [row.studentId, row._count._all]),\n );\n\n const withSignals = (user: (typeof cls.students)[number]) => ({\n ...user,\n signals: classifyUser(user),\n submissionsInClass: submissionCount.get(user.id) ?? 0,\n });\n\n return {\n ...cls,\n teachers: cls.teachers.map(withSignals),\n students: cls.students.map(withSignals),\n announcements: cls.announcements.map((announcement) => ({\n ...announcement,\n remarks:\n announcement.remarks.length > 160\n ? `${announcement.remarks.slice(0, 160)}…`\n : announcement.remarks,\n })),\n suspectedMembers: [...cls.teachers, ...cls.students].filter(\n (user) => classifyUser(user).suspected,\n ).length,\n };\n}\n\n/** Classes a single user teaches or studies in, for the user detail page. */\nexport async function listClassesForUserForAdmin(userId: string) {\n const select = {\n id: true,\n name: true,\n subject: true,\n section: true,\n school: { select: { id: true, name: true } },\n _count: { select: { students: true, assignments: true } },\n } satisfies Prisma.ClassSelect;\n\n const [teaching, enrolled] = await Promise.all([\n prisma.class.findMany({\n where: { teachers: { some: { id: userId } } },\n orderBy: { name: 'asc' },\n take: 100,\n select,\n }),\n prisma.class.findMany({\n where: { students: { some: { id: userId } } },\n orderBy: { name: 'asc' },\n take: 100,\n select,\n }),\n ]);\n\n return { teaching, enrolled };\n}\n"],"names":[],"mappings":"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","debug_id":"952402d2-ba9b-50c7-b9c7-3bb46a71d8a6"}
@@ -112,6 +112,7 @@ export declare function listSchoolDevelopmentProgramsForAdmin(input: {
112
112
  query?: string;
113
113
  }): Promise<{
114
114
  items: {
115
+ signals: import("./adminSpam.js").SpamSignals;
115
116
  type: string;
116
117
  status: string;
117
118
  id: string;
@@ -142,6 +143,7 @@ export declare function listEarlyAccessRequestsForAdmin(input: {
142
143
  pageSize: number;
143
144
  }): Promise<{
144
145
  items: {
146
+ signals: import("./adminSpam.js").SpamSignals;
145
147
  id: string;
146
148
  createdAt: Date;
147
149
  email: string;
@@ -1 +1 @@
1
- {"version":3,"file":"adminInstitution.d.ts","sourceRoot":"/","sources":["services/adminInstitution.ts"],"names":[],"mappings":"AAIA,wBAAsB,mBAAmB,CAAC,KAAK,EAAE;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;;;;;;;;;;;;;;;;;;;;;GAsCA;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDjD;AAED,wBAAsB,uBAAuB,CAAC,KAAK,EAAE;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;;;;;;;;;;;;;;;;;;;;;;;;GA0BA;AAED,wBAAsB,qCAAqC,CAAC,KAAK,EAAE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BA;AAED,wBAAsB,+BAA+B,CAAC,KAAK,EAAE;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;;;;;;;;;;;GAmBA"}
1
+ {"version":3,"file":"adminInstitution.d.ts","sourceRoot":"/","sources":["services/adminInstitution.ts"],"names":[],"mappings":"AAKA,wBAAsB,mBAAmB,CAAC,KAAK,EAAE;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;;;;;;;;;;;;;;;;;;;;;GAsCA;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDjD;AAED,wBAAsB,uBAAuB,CAAC,KAAK,EAAE;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;;;;;;;;;;;;;;;;;;;;;;;;GA0BA;AAED,wBAAsB,qCAAqC,CAAC,KAAK,EAAE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCA;AAED,wBAAsB,+BAA+B,CAAC,KAAK,EAAE;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;;;;;;;;;;;;GAsBA"}
@@ -1,7 +1,8 @@
1
1
 
2
- !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]="5c27592b-412d-5c1e-8999-32503baea449")}catch(e){}}();
2
+ !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]="561863c5-04b0-50db-928f-4d6ba72bd149")}catch(e){}}();
3
3
  import { TRPCError } from '@trpc/server';
4
4
  import { prisma } from '../lib/prisma.js';
5
+ import { classifyEmail } from './adminSpam.js';
5
6
  export async function listSchoolsForAdmin(input) {
6
7
  const skip = input.page * input.pageSize;
7
8
  const where = {};
@@ -138,7 +139,10 @@ export async function listSchoolDevelopmentProgramsForAdmin(input) {
138
139
  prisma.schoolDevelopementProgram.count({ where }),
139
140
  ]);
140
141
  return {
141
- items,
142
+ items: items.map((item) => ({
143
+ ...item,
144
+ signals: classifyEmail(item.contactEmail),
145
+ })),
142
146
  total,
143
147
  page: input.page,
144
148
  pageSize: input.pageSize,
@@ -156,7 +160,10 @@ export async function listEarlyAccessRequestsForAdmin(input) {
156
160
  prisma.earlyAccessRequest.count(),
157
161
  ]);
158
162
  return {
159
- items,
163
+ items: items.map((item) => ({
164
+ ...item,
165
+ signals: classifyEmail(item.email),
166
+ })),
160
167
  total,
161
168
  page: input.page,
162
169
  pageSize: input.pageSize,
@@ -164,4 +171,4 @@ export async function listEarlyAccessRequestsForAdmin(input) {
164
171
  };
165
172
  }
166
173
  //# sourceMappingURL=adminInstitution.js.map
167
- //# debugId=5c27592b-412d-5c1e-8999-32503baea449
174
+ //# debugId=561863c5-04b0-50db-928f-4d6ba72bd149
@@ -1 +1 @@
1
- {"version":3,"file":"adminInstitution.js","sources":["services/adminInstitution.ts"],"sourceRoot":"/","sourcesContent":["import type { Prisma } from '@prisma/client';\nimport { TRPCError } from '@trpc/server';\nimport { prisma } from '../lib/prisma.js';\n\nexport async function listSchoolsForAdmin(input: {\n page: number;\n pageSize: number;\n query?: string;\n}) {\n const skip = input.page * input.pageSize;\n const where: Prisma.SchoolWhereInput = {};\n const q = input.query?.trim();\n if (q) {\n where.OR = [\n { name: { contains: q, mode: 'insensitive' } },\n { subdomain: { contains: q, mode: 'insensitive' } },\n { description: { contains: q, mode: 'insensitive' } },\n ];\n }\n\n const [items, total] = await Promise.all([\n prisma.school.findMany({\n where,\n orderBy: { createdAt: 'desc' },\n skip,\n take: input.pageSize,\n include: {\n _count: {\n select: {\n memberships: true,\n classes: true,\n departments: true,\n },\n },\n },\n }),\n prisma.school.count({ where }),\n ]);\n\n return {\n items,\n total,\n page: input.page,\n pageSize: input.pageSize,\n pageCount: Math.max(1, Math.ceil(total / input.pageSize)),\n };\n}\n\nexport async function getSchoolForAdmin(id: string) {\n const school = await prisma.school.findUnique({\n where: { id },\n include: {\n _count: {\n select: { memberships: true, classes: true, departments: true },\n },\n memberships: {\n orderBy: { createdAt: 'desc' },\n take: 100,\n select: {\n id: true,\n role: true,\n status: true,\n createdAt: true,\n user: {\n select: { id: true, username: true, email: true, verified: true },\n },\n },\n },\n classes: {\n orderBy: { name: 'asc' },\n take: 100,\n select: {\n id: true,\n name: true,\n subject: true,\n section: true,\n color: true,\n department: { select: { id: true, name: true } },\n _count: { select: { students: true, teachers: true } },\n },\n },\n departments: {\n orderBy: { name: 'asc' },\n select: {\n id: true,\n name: true,\n color: true,\n _count: { select: { classes: true, members: true } },\n },\n },\n },\n });\n\n if (!school) {\n throw new TRPCError({ code: 'NOT_FOUND', message: 'School not found' });\n }\n return school;\n}\n\nexport async function listDepartmentsForAdmin(input: {\n page: number;\n pageSize: number;\n schoolId?: string;\n}) {\n const skip = input.page * input.pageSize;\n const where: Prisma.DepartmentWhereInput = {};\n if (input.schoolId) where.schoolId = input.schoolId;\n\n const [items, total] = await Promise.all([\n prisma.department.findMany({\n where,\n orderBy: [{ schoolId: 'asc' }, { name: 'asc' }],\n skip,\n take: input.pageSize,\n include: {\n school: { select: { id: true, name: true, subdomain: true } },\n _count: { select: { classes: true, members: true } },\n },\n }),\n prisma.department.count({ where }),\n ]);\n\n return {\n items,\n total,\n page: input.page,\n pageSize: input.pageSize,\n pageCount: Math.max(1, Math.ceil(total / input.pageSize)),\n };\n}\n\nexport async function listSchoolDevelopmentProgramsForAdmin(input: {\n page: number;\n pageSize: number;\n status?: string;\n query?: string;\n}) {\n const skip = input.page * input.pageSize;\n const where: Prisma.SchoolDevelopementProgramWhereInput = {};\n if (input.status?.trim()) where.status = input.status.trim();\n const q = input.query?.trim();\n if (q) {\n where.OR = [\n { name: { contains: q, mode: 'insensitive' } },\n { city: { contains: q, mode: 'insensitive' } },\n { country: { contains: q, mode: 'insensitive' } },\n { contactEmail: { contains: q, mode: 'insensitive' } },\n ];\n }\n\n const [items, total] = await Promise.all([\n prisma.schoolDevelopementProgram.findMany({\n where,\n orderBy: { submittedAt: 'desc' },\n skip,\n take: input.pageSize,\n }),\n prisma.schoolDevelopementProgram.count({ where }),\n ]);\n\n return {\n items,\n total,\n page: input.page,\n pageSize: input.pageSize,\n pageCount: Math.max(1, Math.ceil(total / input.pageSize)),\n };\n}\n\nexport async function listEarlyAccessRequestsForAdmin(input: {\n page: number;\n pageSize: number;\n}) {\n const skip = input.page * input.pageSize;\n\n const [items, total] = await Promise.all([\n prisma.earlyAccessRequest.findMany({\n orderBy: { createdAt: 'desc' },\n skip,\n take: input.pageSize,\n }),\n prisma.earlyAccessRequest.count(),\n ]);\n\n return {\n items,\n total,\n page: input.page,\n pageSize: input.pageSize,\n pageCount: Math.max(1, Math.ceil(total / input.pageSize)),\n };\n}\n"],"names":[],"mappings":"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","debug_id":"5c27592b-412d-5c1e-8999-32503baea449"}
1
+ {"version":3,"file":"adminInstitution.js","sources":["services/adminInstitution.ts"],"sourceRoot":"/","sourcesContent":["import type { Prisma } from '@prisma/client';\nimport { TRPCError } from '@trpc/server';\nimport { prisma } from '../lib/prisma.js';\nimport { classifyEmail } from './adminSpam.js';\n\nexport async function listSchoolsForAdmin(input: {\n page: number;\n pageSize: number;\n query?: string;\n}) {\n const skip = input.page * input.pageSize;\n const where: Prisma.SchoolWhereInput = {};\n const q = input.query?.trim();\n if (q) {\n where.OR = [\n { name: { contains: q, mode: 'insensitive' } },\n { subdomain: { contains: q, mode: 'insensitive' } },\n { description: { contains: q, mode: 'insensitive' } },\n ];\n }\n\n const [items, total] = await Promise.all([\n prisma.school.findMany({\n where,\n orderBy: { createdAt: 'desc' },\n skip,\n take: input.pageSize,\n include: {\n _count: {\n select: {\n memberships: true,\n classes: true,\n departments: true,\n },\n },\n },\n }),\n prisma.school.count({ where }),\n ]);\n\n return {\n items,\n total,\n page: input.page,\n pageSize: input.pageSize,\n pageCount: Math.max(1, Math.ceil(total / input.pageSize)),\n };\n}\n\nexport async function getSchoolForAdmin(id: string) {\n const school = await prisma.school.findUnique({\n where: { id },\n include: {\n _count: {\n select: { memberships: true, classes: true, departments: true },\n },\n memberships: {\n orderBy: { createdAt: 'desc' },\n take: 100,\n select: {\n id: true,\n role: true,\n status: true,\n createdAt: true,\n user: {\n select: { id: true, username: true, email: true, verified: true },\n },\n },\n },\n classes: {\n orderBy: { name: 'asc' },\n take: 100,\n select: {\n id: true,\n name: true,\n subject: true,\n section: true,\n color: true,\n department: { select: { id: true, name: true } },\n _count: { select: { students: true, teachers: true } },\n },\n },\n departments: {\n orderBy: { name: 'asc' },\n select: {\n id: true,\n name: true,\n color: true,\n _count: { select: { classes: true, members: true } },\n },\n },\n },\n });\n\n if (!school) {\n throw new TRPCError({ code: 'NOT_FOUND', message: 'School not found' });\n }\n return school;\n}\n\nexport async function listDepartmentsForAdmin(input: {\n page: number;\n pageSize: number;\n schoolId?: string;\n}) {\n const skip = input.page * input.pageSize;\n const where: Prisma.DepartmentWhereInput = {};\n if (input.schoolId) where.schoolId = input.schoolId;\n\n const [items, total] = await Promise.all([\n prisma.department.findMany({\n where,\n orderBy: [{ schoolId: 'asc' }, { name: 'asc' }],\n skip,\n take: input.pageSize,\n include: {\n school: { select: { id: true, name: true, subdomain: true } },\n _count: { select: { classes: true, members: true } },\n },\n }),\n prisma.department.count({ where }),\n ]);\n\n return {\n items,\n total,\n page: input.page,\n pageSize: input.pageSize,\n pageCount: Math.max(1, Math.ceil(total / input.pageSize)),\n };\n}\n\nexport async function listSchoolDevelopmentProgramsForAdmin(input: {\n page: number;\n pageSize: number;\n status?: string;\n query?: string;\n}) {\n const skip = input.page * input.pageSize;\n const where: Prisma.SchoolDevelopementProgramWhereInput = {};\n if (input.status?.trim()) where.status = input.status.trim();\n const q = input.query?.trim();\n if (q) {\n where.OR = [\n { name: { contains: q, mode: 'insensitive' } },\n { city: { contains: q, mode: 'insensitive' } },\n { country: { contains: q, mode: 'insensitive' } },\n { contactEmail: { contains: q, mode: 'insensitive' } },\n ];\n }\n\n const [items, total] = await Promise.all([\n prisma.schoolDevelopementProgram.findMany({\n where,\n orderBy: { submittedAt: 'desc' },\n skip,\n take: input.pageSize,\n }),\n prisma.schoolDevelopementProgram.count({ where }),\n ]);\n\n return {\n items: items.map((item) => ({\n ...item,\n signals: classifyEmail(item.contactEmail),\n })),\n total,\n page: input.page,\n pageSize: input.pageSize,\n pageCount: Math.max(1, Math.ceil(total / input.pageSize)),\n };\n}\n\nexport async function listEarlyAccessRequestsForAdmin(input: {\n page: number;\n pageSize: number;\n}) {\n const skip = input.page * input.pageSize;\n\n const [items, total] = await Promise.all([\n prisma.earlyAccessRequest.findMany({\n orderBy: { createdAt: 'desc' },\n skip,\n take: input.pageSize,\n }),\n prisma.earlyAccessRequest.count(),\n ]);\n\n return {\n items: items.map((item) => ({\n ...item,\n signals: classifyEmail(item.email),\n })),\n total,\n page: input.page,\n pageSize: input.pageSize,\n pageCount: Math.max(1, Math.ceil(total / input.pageSize)),\n };\n}\n"],"names":[],"mappings":"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","debug_id":"561863c5-04b0-50db-928f-4d6ba72bd149"}
@@ -2,6 +2,14 @@ export declare function getOverviewMetricsForAdmin(): Promise<{
2
2
  schools: number;
3
3
  users: number;
4
4
  verifiedUsers: number;
5
+ suspectedUsers: number;
6
+ cleanUsers: number;
7
+ signupsLast24h: number;
8
+ signupsLast7d: number;
9
+ activeUsersLast24h: number;
10
+ activeUsersLast7d: number;
11
+ activeClassesLast7d: number;
12
+ submissionsLast24h: number;
5
13
  classes: number;
6
14
  departments: number;
7
15
  runJobs: {
@@ -1 +1 @@
1
- {"version":3,"file":"adminMetrics.d.ts","sourceRoot":"/","sources":["services/adminMetrics.ts"],"names":[],"mappings":"AAEA,wBAAsB,0BAA0B;;;;;;;;;;;;;;;GA2C/C"}
1
+ {"version":3,"file":"adminMetrics.d.ts","sourceRoot":"/","sources":["services/adminMetrics.ts"],"names":[],"mappings":"AAWA,wBAAsB,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;GAqE/C"}
@@ -1,13 +1,32 @@
1
1
 
2
- !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]="0d16ae52-13c5-586e-b648-4a72913e81c7")}catch(e){}}();
2
+ !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]="dc189276-d8bc-50e0-8846-5b673df29328")}catch(e){}}();
3
3
  import { prisma } from '../lib/prisma.js';
4
+ import { suspectedUserWhere } from './adminSpam.js';
5
+ async function countActiveUsersSince(since) {
6
+ const rows = await prisma.session.groupBy({
7
+ by: ['userId'],
8
+ where: { type: 'AUTH', createdAt: { gte: since }, userId: { not: null } },
9
+ });
10
+ return rows.length;
11
+ }
4
12
  export async function getOverviewMetricsForAdmin() {
5
13
  const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
6
- const [schools, users, verifiedUsers, classes, departments, runJobsByStatus, failedRunJobsLast24h, earlyAccessRequests, pendingDevProgramApplications,] = await Promise.all([
14
+ const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
15
+ const suspected = suspectedUserWhere();
16
+ const [schools, users, verifiedUsers, suspectedUsers, signupsLast24h, signupsLast7d, activeUsersLast24h, activeUsersLast7d, classes, activeClassesLast7d, submissionsLast24h, departments, runJobsByStatus, failedRunJobsLast24h, earlyAccessRequests, pendingDevProgramApplications,] = await Promise.all([
7
17
  prisma.school.count(),
8
18
  prisma.user.count(),
9
19
  prisma.user.count({ where: { verified: true } }),
20
+ prisma.user.count({ where: suspected }),
21
+ prisma.userProfile.count({ where: { createdAt: { gte: dayAgo } } }),
22
+ prisma.userProfile.count({ where: { createdAt: { gte: weekAgo } } }),
23
+ countActiveUsersSince(dayAgo),
24
+ countActiveUsersSince(weekAgo),
10
25
  prisma.class.count(),
26
+ prisma.class.count({
27
+ where: { assignments: { some: { createdAt: { gte: weekAgo } } } },
28
+ }),
29
+ prisma.submission.count({ where: { submittedAt: { gte: dayAgo } } }),
11
30
  prisma.department.count(),
12
31
  prisma.runJob.groupBy({ by: ['status'], _count: { _all: true } }),
13
32
  prisma.runJob.count({
@@ -20,6 +39,14 @@ export async function getOverviewMetricsForAdmin() {
20
39
  schools,
21
40
  users,
22
41
  verifiedUsers,
42
+ suspectedUsers,
43
+ cleanUsers: users - suspectedUsers,
44
+ signupsLast24h,
45
+ signupsLast7d,
46
+ activeUsersLast24h,
47
+ activeUsersLast7d,
48
+ activeClassesLast7d,
49
+ submissionsLast24h,
23
50
  classes,
24
51
  departments,
25
52
  runJobs: {
@@ -34,4 +61,4 @@ export async function getOverviewMetricsForAdmin() {
34
61
  };
35
62
  }
36
63
  //# sourceMappingURL=adminMetrics.js.map
37
- //# debugId=0d16ae52-13c5-586e-b648-4a72913e81c7
64
+ //# debugId=dc189276-d8bc-50e0-8846-5b673df29328
@@ -1 +1 @@
1
- {"version":3,"file":"adminMetrics.js","sources":["services/adminMetrics.ts"],"sourceRoot":"/","sourcesContent":["import { prisma } from '../lib/prisma.js';\n\nexport async function getOverviewMetricsForAdmin() {\n const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);\n\n const [\n schools,\n users,\n verifiedUsers,\n classes,\n departments,\n runJobsByStatus,\n failedRunJobsLast24h,\n earlyAccessRequests,\n pendingDevProgramApplications,\n ] = await Promise.all([\n prisma.school.count(),\n prisma.user.count(),\n prisma.user.count({ where: { verified: true } }),\n prisma.class.count(),\n prisma.department.count(),\n prisma.runJob.groupBy({ by: ['status'], _count: { _all: true } }),\n prisma.runJob.count({\n where: { status: 'FAILED', createdAt: { gte: dayAgo } },\n }),\n prisma.earlyAccessRequest.count(),\n prisma.schoolDevelopementProgram.count({ where: { status: 'PENDING' } }),\n ]);\n\n return {\n schools,\n users,\n verifiedUsers,\n classes,\n departments,\n runJobs: {\n byStatus: runJobsByStatus.map((row) => ({\n status: row.status,\n count: row._count._all,\n })),\n failedLast24h: failedRunJobsLast24h,\n },\n earlyAccessRequests,\n pendingDevProgramApplications,\n };\n}\n"],"names":[],"mappings":";;AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,MAAM,CAAC,KAAK,UAAU,0BAA0B;IAC9C,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAE1D,MAAM,CACJ,OAAO,EACP,KAAK,EACL,aAAa,EACb,OAAO,EACP,WAAW,EACX,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,6BAA6B,EAC9B,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACpB,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE;QACrB,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE;QACnB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC;QAChD,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE;QACpB,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE;QACzB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;QACjE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;YAClB,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE;SACxD,CAAC;QACF,MAAM,CAAC,kBAAkB,CAAC,KAAK,EAAE;QACjC,MAAM,CAAC,yBAAyB,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,CAAC;KACzE,CAAC,CAAC;IAEH,OAAO;QACL,OAAO;QACP,KAAK;QACL,aAAa;QACb,OAAO;QACP,WAAW;QACX,OAAO,EAAE;YACP,QAAQ,EAAE,eAAe,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBACtC,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI;aACvB,CAAC,CAAC;YACH,aAAa,EAAE,oBAAoB;SACpC;QACD,mBAAmB;QACnB,6BAA6B;KAC9B,CAAC;AACJ,CAAC","debug_id":"0d16ae52-13c5-586e-b648-4a72913e81c7"}
1
+ {"version":3,"file":"adminMetrics.js","sources":["services/adminMetrics.ts"],"sourceRoot":"/","sourcesContent":["import { prisma } from '../lib/prisma.js';\nimport { suspectedUserWhere } from './adminSpam.js';\n\nasync function countActiveUsersSince(since: Date) {\n const rows = await prisma.session.groupBy({\n by: ['userId'],\n where: { type: 'AUTH', createdAt: { gte: since }, userId: { not: null } },\n });\n return rows.length;\n}\n\nexport async function getOverviewMetricsForAdmin() {\n const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);\n const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);\n const suspected = suspectedUserWhere();\n\n const [\n schools,\n users,\n verifiedUsers,\n suspectedUsers,\n signupsLast24h,\n signupsLast7d,\n activeUsersLast24h,\n activeUsersLast7d,\n classes,\n activeClassesLast7d,\n submissionsLast24h,\n departments,\n runJobsByStatus,\n failedRunJobsLast24h,\n earlyAccessRequests,\n pendingDevProgramApplications,\n ] = await Promise.all([\n prisma.school.count(),\n prisma.user.count(),\n prisma.user.count({ where: { verified: true } }),\n prisma.user.count({ where: suspected }),\n prisma.userProfile.count({ where: { createdAt: { gte: dayAgo } } }),\n prisma.userProfile.count({ where: { createdAt: { gte: weekAgo } } }),\n countActiveUsersSince(dayAgo),\n countActiveUsersSince(weekAgo),\n prisma.class.count(),\n prisma.class.count({\n where: { assignments: { some: { createdAt: { gte: weekAgo } } } },\n }),\n prisma.submission.count({ where: { submittedAt: { gte: dayAgo } } }),\n prisma.department.count(),\n prisma.runJob.groupBy({ by: ['status'], _count: { _all: true } }),\n prisma.runJob.count({\n where: { status: 'FAILED', createdAt: { gte: dayAgo } },\n }),\n prisma.earlyAccessRequest.count(),\n prisma.schoolDevelopementProgram.count({ where: { status: 'PENDING' } }),\n ]);\n\n return {\n schools,\n users,\n verifiedUsers,\n suspectedUsers,\n cleanUsers: users - suspectedUsers,\n signupsLast24h,\n signupsLast7d,\n activeUsersLast24h,\n activeUsersLast7d,\n activeClassesLast7d,\n submissionsLast24h,\n classes,\n departments,\n runJobs: {\n byStatus: runJobsByStatus.map((row) => ({\n status: row.status,\n count: row._count._all,\n })),\n failedLast24h: failedRunJobsLast24h,\n },\n earlyAccessRequests,\n pendingDevProgramApplications,\n };\n}\n"],"names":[],"mappings":"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","debug_id":"dc189276-d8bc-50e0-8846-5b673df29328"}
@@ -0,0 +1,76 @@
1
+ import type { Prisma } from '@prisma/client';
2
+ export type SpamReason = 'BLOCKED_DOMAIN' | 'BOT_PATTERN' | 'DISPOSABLE_DOMAIN' | 'NEVER_VERIFIED_INACTIVE';
3
+ export type SpamSignals = {
4
+ suspected: boolean;
5
+ reasons: SpamReason[];
6
+ };
7
+ export declare function spamRules(): {
8
+ blockedDomains: string[];
9
+ botPatterns: string[];
10
+ disposableDomains: string[];
11
+ };
12
+ export type ClassifiableUser = {
13
+ username: string;
14
+ email: string;
15
+ verified: boolean;
16
+ _count?: {
17
+ teacherIn: number;
18
+ studentIn: number;
19
+ submissions?: number;
20
+ };
21
+ };
22
+ export declare function classifyEmail(email: string | null | undefined, username?: string | null): SpamSignals;
23
+ export declare function classifyUser(user: ClassifiableUser): SpamSignals;
24
+ /**
25
+ * Prisma filter matching the rule-based part of {@link classifyUser}. The
26
+ * `NEVER_VERIFIED_INACTIVE` reason is intentionally excluded: brand new real
27
+ * signups look identical to it, so it only annotates rows and never drives
28
+ * list filtering or counts.
29
+ */
30
+ export declare function suspectedUserWhere(): Prisma.UserWhereInput;
31
+ export declare function notSuspectedUserWhere(): Prisma.UserWhereInput;
32
+ export declare function countSuspectedUsers(): Promise<number>;
33
+ export declare function listUserEmailDomainsForAdmin(input: {
34
+ limit: number;
35
+ suspectedOnly: boolean;
36
+ }): Promise<{
37
+ items: {
38
+ domain: string;
39
+ total: number;
40
+ verified: number;
41
+ suspected: boolean;
42
+ }[];
43
+ }>;
44
+ export declare function getSpamOverviewForAdmin(): Promise<{
45
+ rules: {
46
+ blockedDomains: string[];
47
+ botPatterns: string[];
48
+ disposableDomains: string[];
49
+ };
50
+ totalUsers: number;
51
+ suspectedUsers: number;
52
+ suspectedVerified: number;
53
+ cleanUsers: number;
54
+ domains: {
55
+ domain: string;
56
+ total: number;
57
+ verified: number;
58
+ suspected: boolean;
59
+ }[];
60
+ recent: {
61
+ signals: SpamSignals;
62
+ id: string;
63
+ username: string;
64
+ email: string;
65
+ verified: boolean;
66
+ profile: {
67
+ createdAt: Date;
68
+ } | null;
69
+ _count: {
70
+ teacherIn: number;
71
+ studentIn: number;
72
+ submissions: number;
73
+ };
74
+ }[];
75
+ }>;
76
+ //# sourceMappingURL=adminSpam.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adminSpam.d.ts","sourceRoot":"/","sources":["services/adminSpam.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAG7C,MAAM,MAAM,UAAU,GAClB,gBAAgB,GAChB,aAAa,GACb,mBAAmB,GACnB,yBAAyB,CAAC;AAE9B,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB,CAAC;AA4BF,wBAAgB,SAAS;;;;EASxB;AAWD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACzE,CAAC;AAEF,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GACvB,WAAW,CAwBb;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,WAAW,CAchE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,cAAc,CAkB1D;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAAC,cAAc,CAE7D;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAE3D;AAID,wBAAsB,4BAA4B,CAAC,KAAK,EAAE;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;CACxB;;;;;;;GA4BA;AAED,wBAAsB,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0C5C"}