@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 @@
1
+ {"version":3,"file":"adminTelemetry.js","sources":["services/adminTelemetry.ts"],"sourceRoot":"/","sourcesContent":["import { Prisma } from '@prisma/client';\nimport { prisma } from '../lib/prisma.js';\n\nexport type DailyPoint = {\n date: string;\n signups: number;\n logins: number;\n assignments: number;\n submissions: number;\n messages: number;\n};\n\ntype DayRow = { day: Date; total: bigint };\n\nfunction dayKey(date: Date): string {\n return date.toISOString().slice(0, 10);\n}\n\nasync function dailyCounts(\n table: Prisma.Sql,\n column: Prisma.Sql,\n since: Date,\n extra: Prisma.Sql = Prisma.empty,\n): Promise<Map<string, number>> {\n const rows = await prisma.$queryRaw<DayRow[]>`\n SELECT date_trunc('day', ${column}) AS day, count(*) AS total\n FROM ${table}\n WHERE ${column} >= ${since} ${extra}\n GROUP BY 1\n `;\n return new Map(rows.map((row) => [dayKey(row.day), Number(row.total)]));\n}\n\nasync function dailyDistinctLogins(since: Date): Promise<Map<string, number>> {\n const rows = await prisma.$queryRaw<DayRow[]>`\n SELECT date_trunc('day', \"createdAt\") AS day,\n count(DISTINCT \"userId\") AS total\n FROM \"Session\"\n WHERE \"createdAt\" >= ${since} AND \"type\" = 'AUTH' AND \"userId\" IS NOT NULL\n GROUP BY 1\n `;\n return new Map(rows.map((row) => [dayKey(row.day), Number(row.total)]));\n}\n\n/**\n * Per-day platform activity for the last `days` days, zero-filled so the admin\n * chart always has one point per day even when nothing happened.\n */\nexport async function getActivitySeriesForAdmin(input: {\n days: number;\n}): Promise<{ points: DailyPoint[] }> {\n const days = Math.min(Math.max(input.days, 1), 90);\n const since = new Date(Date.now() - (days - 1) * 24 * 60 * 60 * 1000);\n since.setUTCHours(0, 0, 0, 0);\n\n const [signups, logins, assignments, submissions, messages] =\n await Promise.all([\n dailyCounts(Prisma.sql`\"UserProfile\"`, Prisma.sql`\"createdAt\"`, since),\n dailyDistinctLogins(since),\n dailyCounts(Prisma.sql`\"Assignment\"`, Prisma.sql`\"createdAt\"`, since),\n dailyCounts(Prisma.sql`\"Submission\"`, Prisma.sql`\"createdAt\"`, since),\n dailyCounts(Prisma.sql`\"Message\"`, Prisma.sql`\"createdAt\"`, since),\n ]);\n\n const points: DailyPoint[] = [];\n for (let offset = 0; offset < days; offset += 1) {\n const date = new Date(since.getTime() + offset * 24 * 60 * 60 * 1000);\n const key = dayKey(date);\n points.push({\n date: key,\n signups: signups.get(key) ?? 0,\n logins: logins.get(key) ?? 0,\n assignments: assignments.get(key) ?? 0,\n submissions: submissions.get(key) ?? 0,\n messages: messages.get(key) ?? 0,\n });\n }\n\n return { points };\n}\n\n/** Content inventory plus AI/job health, i.e. \"what is actually on the platform\". */\nexport async function getContentTelemetryForAdmin() {\n const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);\n const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);\n\n const [\n assignments,\n assignmentsLast7d,\n submissions,\n submissionsLast7d,\n gradedSubmissions,\n announcements,\n announcementsLast7d,\n discussions,\n messages,\n messagesLast7d,\n worksheets,\n events,\n modules,\n files,\n fileSize,\n aiCalls7d,\n aiFailures7d,\n aiTokens7d,\n aiByModel7d,\n runJobsByType7d,\n failedJobsLast24h,\n ] = await Promise.all([\n prisma.assignment.count(),\n prisma.assignment.count({ where: { createdAt: { gte: weekAgo } } }),\n prisma.submission.count(),\n prisma.submission.count({ where: { createdAt: { gte: weekAgo } } }),\n prisma.submission.count({ where: { gradeReceived: { not: null } } }),\n prisma.announcement.count(),\n prisma.announcement.count({ where: { createdAt: { gte: weekAgo } } }),\n prisma.discussion.count(),\n prisma.message.count(),\n prisma.message.count({ where: { createdAt: { gte: weekAgo } } }),\n prisma.worksheet.count(),\n prisma.event.count(),\n prisma.module.count(),\n prisma.file.count(),\n prisma.file.aggregate({ _sum: { size: true } }),\n prisma.aiUsageLog.count({ where: { createdAt: { gte: weekAgo } } }),\n prisma.aiUsageLog.count({\n where: { createdAt: { gte: weekAgo }, success: false },\n }),\n prisma.aiUsageLog.aggregate({\n where: { createdAt: { gte: weekAgo } },\n _sum: { totalTokens: true },\n }),\n prisma.aiUsageLog.groupBy({\n by: ['model'],\n where: { createdAt: { gte: weekAgo } },\n _count: { _all: true },\n _sum: { totalTokens: true },\n }),\n prisma.runJob.groupBy({\n by: ['type', 'status'],\n where: { createdAt: { gte: weekAgo } },\n _count: { _all: true },\n }),\n prisma.runJob.count({\n where: { status: 'FAILED', createdAt: { gte: dayAgo } },\n }),\n ]);\n\n const jobsByType = new Map<string, { total: number; failed: number }>();\n for (const row of runJobsByType7d) {\n const entry = jobsByType.get(row.type) ?? { total: 0, failed: 0 };\n entry.total += row._count._all;\n if (row.status === 'FAILED') entry.failed += row._count._all;\n jobsByType.set(row.type, entry);\n }\n\n return {\n content: {\n assignments,\n assignmentsLast7d,\n submissions,\n submissionsLast7d,\n gradedSubmissions,\n announcements,\n announcementsLast7d,\n discussions,\n messages,\n messagesLast7d,\n worksheets,\n events,\n modules,\n files,\n storageBytes: Number(fileSize._sum.size ?? 0),\n },\n ai: {\n calls7d: aiCalls7d,\n failures7d: aiFailures7d,\n tokens7d: aiTokens7d._sum.totalTokens ?? 0,\n byModel: aiByModel7d\n .map((row) => ({\n model: row.model,\n calls: row._count._all,\n tokens: row._sum.totalTokens ?? 0,\n }))\n .sort((a, b) => b.calls - a.calls),\n },\n jobs: {\n failedLast24h: failedJobsLast24h,\n byType: [...jobsByType.entries()]\n .map(([type, counts]) => ({ type, ...counts }))\n .sort((a, b) => b.total - a.total),\n },\n };\n}\n"],"names":[],"mappings":"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","debug_id":"2b411e03-e7aa-506c-883f-1affe1c5e1c5"}
@@ -1,9 +1,19 @@
1
+ export type AdminUserSpamFilter = 'all' | 'suspected' | 'clean';
2
+ export type AdminUserActivityFilter = 'all' | 'active' | 'inactive';
3
+ export type AdminUserSort = 'username' | 'recent' | 'activity';
1
4
  export declare function searchUsersForAdmin(input: {
2
5
  page: number;
3
6
  pageSize: number;
4
7
  query?: string;
8
+ emailDomain?: string;
9
+ schoolId?: string;
10
+ verified?: boolean;
11
+ spam?: AdminUserSpamFilter;
12
+ activity?: AdminUserActivityFilter;
13
+ sort?: AdminUserSort;
5
14
  }): Promise<{
6
15
  items: {
16
+ signals: import("./adminSpam.js").SpamSignals;
7
17
  school: {
8
18
  id: string;
9
19
  name: string;
@@ -12,9 +22,13 @@ export declare function searchUsersForAdmin(input: {
12
22
  username: string;
13
23
  email: string;
14
24
  verified: boolean;
25
+ profile: {
26
+ createdAt: Date;
27
+ } | null;
15
28
  _count: {
16
29
  teacherIn: number;
17
30
  studentIn: number;
31
+ submissions: number;
18
32
  sessions: number;
19
33
  };
20
34
  }[];
@@ -24,6 +38,37 @@ export declare function searchUsersForAdmin(input: {
24
38
  pageCount: number;
25
39
  }>;
26
40
  export declare function getUserForAdmin(id: string): Promise<{
41
+ classes: {
42
+ teaching: {
43
+ school: {
44
+ id: string;
45
+ name: string;
46
+ } | null;
47
+ section: string;
48
+ id: string;
49
+ name: string;
50
+ _count: {
51
+ students: number;
52
+ assignments: number;
53
+ };
54
+ subject: string;
55
+ }[];
56
+ enrolled: {
57
+ school: {
58
+ id: string;
59
+ name: string;
60
+ } | null;
61
+ section: string;
62
+ id: string;
63
+ name: string;
64
+ _count: {
65
+ students: number;
66
+ assignments: number;
67
+ };
68
+ subject: string;
69
+ }[];
70
+ };
71
+ signals: import("./adminSpam.js").SpamSignals;
27
72
  school: {
28
73
  id: string;
29
74
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"adminUser.d.ts","sourceRoot":"/","sources":["services/adminUser.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,eAAe,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+C/C"}
1
+ {"version":3,"file":"adminUser.d.ts","sourceRoot":"/","sources":["services/adminUser.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,WAAW,GAAG,OAAO,CAAC;AAChE,MAAM,MAAM,uBAAuB,GAAG,KAAK,GAAG,QAAQ,GAAG,UAAU,CAAC;AACpE,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE/D,wBAAsB,mBAAmB,CAAC,KAAK,EAAE;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,IAAI,CAAC,EAAE,aAAa,CAAC;CACtB;;;;;;;;;;;;;;;;;;;;;;;;;GA4EA;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiD/C"}
@@ -1,21 +1,51 @@
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]="e2d7385a-4270-5961-b70a-36d37ce736b1")}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]="1d69ff5c-58e2-586c-ad6b-12e61e3603d3")}catch(e){}}();
3
3
  import { TRPCError } from '@trpc/server';
4
4
  import { prisma } from '../lib/prisma.js';
5
+ import { classifyUser, suspectedUserWhere } from './adminSpam.js';
6
+ import { listClassesForUserForAdmin } from './adminClass.js';
5
7
  export async function searchUsersForAdmin(input) {
6
8
  const skip = input.page * input.pageSize;
7
- const where = {};
9
+ const filters = [];
8
10
  const q = input.query?.trim();
9
11
  if (q) {
10
- where.OR = [
11
- { username: { contains: q, mode: 'insensitive' } },
12
- { email: { contains: q, mode: 'insensitive' } },
13
- ];
12
+ filters.push({
13
+ OR: [
14
+ { username: { contains: q, mode: 'insensitive' } },
15
+ { email: { contains: q, mode: 'insensitive' } },
16
+ ],
17
+ });
14
18
  }
19
+ const domain = input.emailDomain?.trim().replace(/^@/, '');
20
+ if (domain) {
21
+ filters.push({ email: { endsWith: `@${domain}`, mode: 'insensitive' } });
22
+ }
23
+ if (input.schoolId)
24
+ filters.push({ schoolId: input.schoolId });
25
+ if (input.verified !== undefined)
26
+ filters.push({ verified: input.verified });
27
+ if (input.spam === 'suspected')
28
+ filters.push(suspectedUserWhere());
29
+ if (input.spam === 'clean')
30
+ filters.push({ NOT: suspectedUserWhere() });
31
+ // "Active" means the account is attached to at least one class — the cheapest
32
+ // proxy for a real account that is actually using the platform.
33
+ if (input.activity === 'active') {
34
+ filters.push({ OR: [{ teacherIn: { some: {} } }, { studentIn: { some: {} } }] });
35
+ }
36
+ if (input.activity === 'inactive') {
37
+ filters.push({ teacherIn: { none: {} }, studentIn: { none: {} } });
38
+ }
39
+ const where = filters.length ? { AND: filters } : {};
40
+ const orderBy = input.sort === 'recent'
41
+ ? [{ profile: { createdAt: 'desc' } }, { username: 'asc' }]
42
+ : input.sort === 'activity'
43
+ ? [{ submissions: { _count: 'desc' } }, { username: 'asc' }]
44
+ : [{ username: 'asc' }];
15
45
  const [items, total] = await Promise.all([
16
46
  prisma.user.findMany({
17
47
  where,
18
- orderBy: { username: 'asc' },
48
+ orderBy,
19
49
  skip,
20
50
  take: input.pageSize,
21
51
  select: {
@@ -23,16 +53,22 @@ export async function searchUsersForAdmin(input) {
23
53
  username: true,
24
54
  email: true,
25
55
  verified: true,
56
+ profile: { select: { createdAt: true } },
26
57
  school: { select: { id: true, name: true } },
27
58
  _count: {
28
- select: { teacherIn: true, studentIn: true, sessions: true },
59
+ select: {
60
+ teacherIn: true,
61
+ studentIn: true,
62
+ submissions: true,
63
+ sessions: { where: { type: 'AUTH' } },
64
+ },
29
65
  },
30
66
  },
31
67
  }),
32
68
  prisma.user.count({ where }),
33
69
  ]);
34
70
  return {
35
- items,
71
+ items: items.map((user) => ({ ...user, signals: classifyUser(user) })),
36
72
  total,
37
73
  page: input.page,
38
74
  pageSize: input.pageSize,
@@ -84,7 +120,8 @@ export async function getUserForAdmin(id) {
84
120
  if (!user) {
85
121
  throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
86
122
  }
87
- return user;
123
+ const classes = await listClassesForUserForAdmin(id);
124
+ return { ...user, classes, signals: classifyUser(user) };
88
125
  }
89
126
  //# sourceMappingURL=adminUser.js.map
90
- //# debugId=e2d7385a-4270-5961-b70a-36d37ce736b1
127
+ //# debugId=1d69ff5c-58e2-586c-ad6b-12e61e3603d3
@@ -1 +1 @@
1
- {"version":3,"file":"adminUser.js","sources":["services/adminUser.ts"],"sourceRoot":"/","sourcesContent":["import type { Prisma } from '@prisma/client';\nimport { TRPCError } from '@trpc/server';\nimport { prisma } from '../lib/prisma.js';\n\nexport async function searchUsersForAdmin(input: {\n page: number;\n pageSize: number;\n query?: string;\n}) {\n const skip = input.page * input.pageSize;\n const where: Prisma.UserWhereInput = {};\n const q = input.query?.trim();\n if (q) {\n where.OR = [\n { username: { contains: q, mode: 'insensitive' } },\n { email: { contains: q, mode: 'insensitive' } },\n ];\n }\n\n const [items, total] = await Promise.all([\n prisma.user.findMany({\n where,\n orderBy: { username: 'asc' },\n skip,\n take: input.pageSize,\n select: {\n id: true,\n username: true,\n email: true,\n verified: true,\n school: { select: { id: true, name: true } },\n _count: {\n select: { teacherIn: true, studentIn: true, sessions: true },\n },\n },\n }),\n prisma.user.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 getUserForAdmin(id: string) {\n const user = await prisma.user.findUnique({\n where: { id },\n select: {\n id: true,\n username: true,\n email: true,\n verified: true,\n termsAcceptedAt: true,\n termsVersion: true,\n profile: {\n select: { displayName: true, location: true, createdAt: true },\n },\n school: { select: { id: true, name: true, subdomain: true } },\n schoolMemberships: {\n select: {\n id: true,\n role: true,\n status: true,\n createdAt: true,\n school: { select: { id: true, name: true } },\n },\n },\n sessions: {\n where: { type: 'AUTH' },\n orderBy: { createdAt: 'desc' },\n take: 10,\n // Session.id is the bearer token — never return it from admin APIs.\n select: { type: true, createdAt: true, expiresAt: true },\n },\n _count: {\n select: {\n teacherIn: true,\n studentIn: true,\n // Keep the count consistent with the AUTH-only session list above.\n sessions: { where: { type: 'AUTH' } },\n submissions: true,\n files: true,\n },\n },\n },\n });\n\n if (!user) {\n throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });\n }\n return user;\n}\n"],"names":[],"mappings":"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","debug_id":"e2d7385a-4270-5961-b70a-36d37ce736b1"}
1
+ {"version":3,"file":"adminUser.js","sources":["services/adminUser.ts"],"sourceRoot":"/","sourcesContent":["import type { Prisma } from '@prisma/client';\nimport { TRPCError } from '@trpc/server';\nimport { prisma } from '../lib/prisma.js';\nimport { classifyUser, suspectedUserWhere } from './adminSpam.js';\nimport { listClassesForUserForAdmin } from './adminClass.js';\n\nexport type AdminUserSpamFilter = 'all' | 'suspected' | 'clean';\nexport type AdminUserActivityFilter = 'all' | 'active' | 'inactive';\nexport type AdminUserSort = 'username' | 'recent' | 'activity';\n\nexport async function searchUsersForAdmin(input: {\n page: number;\n pageSize: number;\n query?: string;\n emailDomain?: string;\n schoolId?: string;\n verified?: boolean;\n spam?: AdminUserSpamFilter;\n activity?: AdminUserActivityFilter;\n sort?: AdminUserSort;\n}) {\n const skip = input.page * input.pageSize;\n const filters: Prisma.UserWhereInput[] = [];\n\n const q = input.query?.trim();\n if (q) {\n filters.push({\n OR: [\n { username: { contains: q, mode: 'insensitive' } },\n { email: { contains: q, mode: 'insensitive' } },\n ],\n });\n }\n\n const domain = input.emailDomain?.trim().replace(/^@/, '');\n if (domain) {\n filters.push({ email: { endsWith: `@${domain}`, mode: 'insensitive' } });\n }\n\n if (input.schoolId) filters.push({ schoolId: input.schoolId });\n if (input.verified !== undefined) filters.push({ verified: input.verified });\n\n if (input.spam === 'suspected') filters.push(suspectedUserWhere());\n if (input.spam === 'clean') filters.push({ NOT: suspectedUserWhere() });\n\n // \"Active\" means the account is attached to at least one class — the cheapest\n // proxy for a real account that is actually using the platform.\n if (input.activity === 'active') {\n filters.push({ OR: [{ teacherIn: { some: {} } }, { studentIn: { some: {} } }] });\n }\n if (input.activity === 'inactive') {\n filters.push({ teacherIn: { none: {} }, studentIn: { none: {} } });\n }\n\n const where: Prisma.UserWhereInput = filters.length ? { AND: filters } : {};\n\n const orderBy: Prisma.UserOrderByWithRelationInput[] =\n input.sort === 'recent'\n ? [{ profile: { createdAt: 'desc' } }, { username: 'asc' }]\n : input.sort === 'activity'\n ? [{ submissions: { _count: 'desc' } }, { username: 'asc' }]\n : [{ username: 'asc' }];\n\n const [items, total] = await Promise.all([\n prisma.user.findMany({\n where,\n orderBy,\n skip,\n take: input.pageSize,\n select: {\n id: true,\n username: true,\n email: true,\n verified: true,\n profile: { select: { createdAt: true } },\n school: { select: { id: true, name: true } },\n _count: {\n select: {\n teacherIn: true,\n studentIn: true,\n submissions: true,\n sessions: { where: { type: 'AUTH' } },\n },\n },\n },\n }),\n prisma.user.count({ where }),\n ]);\n\n return {\n items: items.map((user) => ({ ...user, signals: classifyUser(user) })),\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 getUserForAdmin(id: string) {\n const user = await prisma.user.findUnique({\n where: { id },\n select: {\n id: true,\n username: true,\n email: true,\n verified: true,\n termsAcceptedAt: true,\n termsVersion: true,\n profile: {\n select: { displayName: true, location: true, createdAt: true },\n },\n school: { select: { id: true, name: true, subdomain: true } },\n schoolMemberships: {\n select: {\n id: true,\n role: true,\n status: true,\n createdAt: true,\n school: { select: { id: true, name: true } },\n },\n },\n sessions: {\n where: { type: 'AUTH' },\n orderBy: { createdAt: 'desc' },\n take: 10,\n // Session.id is the bearer token — never return it from admin APIs.\n select: { type: true, createdAt: true, expiresAt: true },\n },\n _count: {\n select: {\n teacherIn: true,\n studentIn: true,\n // Keep the count consistent with the AUTH-only session list above.\n sessions: { where: { type: 'AUTH' } },\n submissions: true,\n files: true,\n },\n },\n },\n });\n\n if (!user) {\n throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });\n }\n\n const classes = await listClassesForUserForAdmin(id);\n return { ...user, classes, signals: classifyUser(user) };\n}\n"],"names":[],"mappings":"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","debug_id":"1d69ff5c-58e2-586c-ad6b-12e61e3603d3"}
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"/","sources":["services/auth.ts"],"names":[],"mappings":"AA8BA,qFAAqF;AACrF,wBAAsB,QAAQ,CAAC,KAAK,EAAE;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,IAAI,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;;;;;GA8EA;AAED,8DAA8D;AAC9D,wBAAsB,KAAK,CAAC,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;GAoCxE;AAED,iEAAiE;AACjE,wBAAsB,MAAM,CAAC,MAAM,EAAE,MAAM;;GAG1C;AAED,0EAA0E;AAC1E,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;;GASzC;AAED,gFAAgF;AAChF,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,MAAM;;GAiC1D;AAED,8EAA8E;AAC9E,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM;;GA+BzC;AAED,kFAAkF;AAClF,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,MAAM;;GA+BvD;AAED,mFAAmF;AACnF,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;GAuBlE"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"/","sources":["services/auth.ts"],"names":[],"mappings":"AA8BA,qFAAqF;AACrF,wBAAsB,QAAQ,CAAC,KAAK,EAAE;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,IAAI,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;;;;;GA8EA;AAED,8DAA8D;AAC9D,wBAAsB,KAAK,CAAC,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;GAoCxE;AAED,iEAAiE;AACjE,wBAAsB,MAAM,CAAC,MAAM,EAAE,MAAM;;GAG1C;AAED,0EAA0E;AAC1E,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;;GASzC;AAED,gFAAgF;AAChF,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,MAAM;;GAoC1D;AAED,8EAA8E;AAC9E,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM;;GA+BzC;AAED,kFAAkF;AAClF,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,MAAM;;GA+BvD;AAED,mFAAmF;AACnF,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;GA0BlE"}
@@ -3,7 +3,7 @@
3
3
  * Handles session creation, Redis caching, and email notifications.
4
4
  */
5
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]="d8981916-8875-5f33-b907-7e722e9a3ce9")}catch(e){}}();
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]="50950b99-0cf5-508a-86f4-0c11cb07851e")}catch(e){}}();
7
7
  import { SessionType } from "@prisma/client";
8
8
  import { TRPCError } from "@trpc/server";
9
9
  import { v4 as uuidv4 } from "uuid";
@@ -130,7 +130,10 @@ export async function resendVerificationEmail(email) {
130
130
  message: "User not found",
131
131
  });
132
132
  }
133
- await deleteSessionsByUserId(user.id);
133
+ // Do NOT delete the user's sessions here — resending a verification email
134
+ // must never log the account out of its active devices. A previously issued
135
+ // verification token simply expires on its own; issuing a fresh one is
136
+ // enough.
134
137
  const verificationToken = await createSession({
135
138
  id: uuidv4(),
136
139
  type: SessionType.EMAIL_VERIFICATION,
@@ -227,8 +230,11 @@ export async function resetPassword(token, password) {
227
230
  });
228
231
  }
229
232
  await updateUserPassword(session.userId, await hash(password, 10));
230
- await deleteSessionById(token);
233
+ // Revoke every session (AUTH + any other reset/verification tokens), not
234
+ // just the consumed reset token, so a stolen bearer token can't outlive a
235
+ // password reset.
236
+ await deleteSessionsByUserId(session.userId);
231
237
  return { success: true };
232
238
  }
233
239
  //# sourceMappingURL=auth.js.map
234
- //# debugId=d8981916-8875-5f33-b907-7e722e9a3ce9
240
+ //# debugId=50950b99-0cf5-508a-86f4-0c11cb07851e
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sources":["services/auth.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Auth service – user registration, login, logout, verification, and password reset.\n * Handles session creation, Redis caching, and email notifications.\n */\nimport { SessionType } from \"@prisma/client\";\nimport { TRPCError } from \"@trpc/server\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { compare, hash } from \"bcryptjs\";\nimport { sendMail } from \"../utils/email.js\";\nimport { prismaWrapper } from \"../utils/prismaWrapper.js\";\nimport { env } from \"../lib/config/env.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { CURRENT_TERMS_VERSION } from \"../lib/legal.js\";\nimport {\n findUserByUsername,\n findUserByUsernameOrEmail,\n findUserByEmail,\n findUserById,\n createUser,\n createSession,\n deleteSessionsByUserId,\n deleteShortLivedSessionsByUserId,\n deleteUserById,\n findSessionByIdAndType,\n findSessionByIdWithUserAndType,\n updateUserVerified,\n updateUserPassword,\n deleteSessionById,\n} from \"../models/auth.js\";\n\n/** Register a new user, create verification session, and send verification email. */\nexport async function register(input: {\n username: string;\n email: string;\n password: string;\n agreeToTerms: true;\n termsVersion: string;\n}) {\n const { username, email, password, termsVersion } = input;\n\n if (termsVersion !== CURRENT_TERMS_VERSION) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Please accept the current Terms of Service and Privacy Policy.\",\n });\n }\n\n const existingUser = await prismaWrapper.findFirst(\n () => findUserByUsernameOrEmail(username, email),\n \"checking for existing user during registration\"\n );\n\n if (existingUser && existingUser.verified) {\n if (existingUser.username === username) {\n throw new TRPCError({\n code: \"CONFLICT\",\n message: \"Username already exists\",\n });\n }\n if (existingUser.email === email) {\n throw new TRPCError({\n code: \"CONFLICT\",\n message: \"Email already exists\",\n });\n }\n } else if (existingUser && !existingUser.verified) {\n await prismaWrapper.deleteMany(\n () => deleteSessionsByUserId(existingUser.id),\n \"deleting existing sessions for unverified user\"\n );\n await prismaWrapper.delete(\n () => deleteUserById(existingUser.id),\n \"deleting unverified user\"\n );\n }\n\n const user = await prismaWrapper.create(\n async () =>\n createUser({\n username,\n email,\n password: await hash(password, 10),\n verified: false,\n termsAcceptedAt: new Date(),\n termsVersion,\n }),\n \"creating new user during registration\"\n );\n\n const verificationToken = await prismaWrapper.create(\n () =>\n createSession({\n id: uuidv4(),\n type: SessionType.EMAIL_VERIFICATION,\n userId: user.id,\n expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30),\n }),\n \"creating verification token\"\n );\n\n try {\n await sendMail({\n from: \"hello@studious.sh\",\n to: user.email,\n subject: \"Verify your email\",\n text: `Click the link to verify your email: ${env.NEXT_PUBLIC_APP_URL}/verify/${verificationToken.id}`,\n });\n } catch (err) {\n logger.error(\"Failed to send verification email\", {\n email: user.email,\n err,\n });\n }\n\n return { user: { id: user.id, username: user.username } };\n}\n\n/** Authenticate user by username/password, create session. */\nexport async function login(input: { username: string; password: string }) {\n const { username, password } = input;\n\n const user = await findUserByUsername(username);\n if (!user) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Invalid username or password\",\n });\n }\n\n if (!(await compare(password, user.password))) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Invalid username or password\",\n });\n }\n\n if (!user.verified) {\n return {\n verified: false,\n user: { email: user.email },\n };\n }\n\n const session = await createSession({\n id: uuidv4(),\n type: SessionType.AUTH,\n userId: user.id,\n expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30),\n });\n\n return {\n token: session.id,\n user: { id: user.id, username: user.username },\n };\n}\n\n/** Delete all sessions for a user (logs them out everywhere). */\nexport async function logout(userId: string) {\n await deleteSessionsByUserId(userId);\n return { success: true };\n}\n\n/** Verify user exists and return profile. Used for session validation. */\nexport async function check(userId: string) {\n const user = await findUserById(userId);\n if (!user) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"User not found\",\n });\n }\n return { user };\n}\n\n/** Resend verification email. Replaces existing sessions and sends new link. */\nexport async function resendVerificationEmail(email: string) {\n const user = await findUserByEmail(email);\n if (!user) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"User not found\",\n });\n }\n\n await deleteSessionsByUserId(user.id);\n\n const verificationToken = await createSession({\n id: uuidv4(),\n type: SessionType.EMAIL_VERIFICATION,\n userId: user.id,\n expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30),\n });\n\n try {\n await sendMail({\n from: \"hello@studious.sh\",\n to: user.email,\n subject: \"Verify your email\",\n text: `Click the link to verify your email: ${env.NEXT_PUBLIC_APP_URL}/verify/${verificationToken.id}`,\n });\n } catch (err) {\n logger.error(\"Failed to send verification email\", {\n email: user.email,\n err,\n });\n }\n\n return { success: true };\n}\n\n/** Verify email via token. Marks user as verified and deletes the session. */\nexport async function verify(token: string) {\n const session = await findSessionByIdAndType(\n token,\n SessionType.EMAIL_VERIFICATION\n );\n if (!session) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Session not found\",\n });\n }\n\n if (session.expiresAt && session.expiresAt < new Date()) {\n await deleteSessionById(token);\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Session expired\",\n });\n }\n\n if (!session.userId) {\n await deleteSessionById(token);\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Invalid session state\",\n });\n }\n\n await updateUserVerified(session.userId, true);\n await deleteSessionById(token);\n return { success: true };\n}\n\n/** Send password reset email. Silently succeeds if email not found (security). */\nexport async function requestPasswordReset(email: string) {\n const user = await findUserByEmail(email);\n if (!user) {\n return { success: true };\n }\n\n const twoHoursFromNow = new Date(Date.now() + 1000 * 60 * 60 * 2);\n await deleteShortLivedSessionsByUserId(user.id, twoHoursFromNow);\n\n const resetToken = await createSession({\n id: uuidv4(),\n type: SessionType.PASSWORD_RESET,\n userId: user.id,\n expiresAt: new Date(Date.now() + 1000 * 60 * 60),\n });\n\n try {\n await sendMail({\n from: \"hello@studious.sh\",\n to: user.email,\n subject: \"Reset your password\",\n text: `Click the link to reset your password: ${env.NEXT_PUBLIC_APP_URL}/reset-password/${resetToken.id}`,\n });\n } catch (err) {\n logger.error(\"Failed to send password reset email\", {\n email: user.email,\n err,\n });\n }\n\n return { success: true };\n}\n\n/** Reset password using token from email. Validates token and updates password. */\nexport async function resetPassword(token: string, password: string) {\n const session = await findSessionByIdWithUserAndType(\n token,\n SessionType.PASSWORD_RESET\n );\n if (!session || !session.userId) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Invalid or expired reset token\",\n });\n }\n\n if (session.expiresAt && session.expiresAt < new Date()) {\n await deleteSessionById(token);\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Reset token has expired\",\n });\n }\n\n await updateUserPassword(session.userId, await hash(password, 10));\n await deleteSessionById(token);\n return { success: true };\n}\n"],"names":[],"mappings":"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","debug_id":"d8981916-8875-5f33-b907-7e722e9a3ce9"}
1
+ {"version":3,"file":"auth.js","sources":["services/auth.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Auth service – user registration, login, logout, verification, and password reset.\n * Handles session creation, Redis caching, and email notifications.\n */\nimport { SessionType } from \"@prisma/client\";\nimport { TRPCError } from \"@trpc/server\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { compare, hash } from \"bcryptjs\";\nimport { sendMail } from \"../utils/email.js\";\nimport { prismaWrapper } from \"../utils/prismaWrapper.js\";\nimport { env } from \"../lib/config/env.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { CURRENT_TERMS_VERSION } from \"../lib/legal.js\";\nimport {\n findUserByUsername,\n findUserByUsernameOrEmail,\n findUserByEmail,\n findUserById,\n createUser,\n createSession,\n deleteSessionsByUserId,\n deleteShortLivedSessionsByUserId,\n deleteUserById,\n findSessionByIdAndType,\n findSessionByIdWithUserAndType,\n updateUserVerified,\n updateUserPassword,\n deleteSessionById,\n} from \"../models/auth.js\";\n\n/** Register a new user, create verification session, and send verification email. */\nexport async function register(input: {\n username: string;\n email: string;\n password: string;\n agreeToTerms: true;\n termsVersion: string;\n}) {\n const { username, email, password, termsVersion } = input;\n\n if (termsVersion !== CURRENT_TERMS_VERSION) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Please accept the current Terms of Service and Privacy Policy.\",\n });\n }\n\n const existingUser = await prismaWrapper.findFirst(\n () => findUserByUsernameOrEmail(username, email),\n \"checking for existing user during registration\"\n );\n\n if (existingUser && existingUser.verified) {\n if (existingUser.username === username) {\n throw new TRPCError({\n code: \"CONFLICT\",\n message: \"Username already exists\",\n });\n }\n if (existingUser.email === email) {\n throw new TRPCError({\n code: \"CONFLICT\",\n message: \"Email already exists\",\n });\n }\n } else if (existingUser && !existingUser.verified) {\n await prismaWrapper.deleteMany(\n () => deleteSessionsByUserId(existingUser.id),\n \"deleting existing sessions for unverified user\"\n );\n await prismaWrapper.delete(\n () => deleteUserById(existingUser.id),\n \"deleting unverified user\"\n );\n }\n\n const user = await prismaWrapper.create(\n async () =>\n createUser({\n username,\n email,\n password: await hash(password, 10),\n verified: false,\n termsAcceptedAt: new Date(),\n termsVersion,\n }),\n \"creating new user during registration\"\n );\n\n const verificationToken = await prismaWrapper.create(\n () =>\n createSession({\n id: uuidv4(),\n type: SessionType.EMAIL_VERIFICATION,\n userId: user.id,\n expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30),\n }),\n \"creating verification token\"\n );\n\n try {\n await sendMail({\n from: \"hello@studious.sh\",\n to: user.email,\n subject: \"Verify your email\",\n text: `Click the link to verify your email: ${env.NEXT_PUBLIC_APP_URL}/verify/${verificationToken.id}`,\n });\n } catch (err) {\n logger.error(\"Failed to send verification email\", {\n email: user.email,\n err,\n });\n }\n\n return { user: { id: user.id, username: user.username } };\n}\n\n/** Authenticate user by username/password, create session. */\nexport async function login(input: { username: string; password: string }) {\n const { username, password } = input;\n\n const user = await findUserByUsername(username);\n if (!user) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Invalid username or password\",\n });\n }\n\n if (!(await compare(password, user.password))) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Invalid username or password\",\n });\n }\n\n if (!user.verified) {\n return {\n verified: false,\n user: { email: user.email },\n };\n }\n\n const session = await createSession({\n id: uuidv4(),\n type: SessionType.AUTH,\n userId: user.id,\n expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30),\n });\n\n return {\n token: session.id,\n user: { id: user.id, username: user.username },\n };\n}\n\n/** Delete all sessions for a user (logs them out everywhere). */\nexport async function logout(userId: string) {\n await deleteSessionsByUserId(userId);\n return { success: true };\n}\n\n/** Verify user exists and return profile. Used for session validation. */\nexport async function check(userId: string) {\n const user = await findUserById(userId);\n if (!user) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"User not found\",\n });\n }\n return { user };\n}\n\n/** Resend verification email. Replaces existing sessions and sends new link. */\nexport async function resendVerificationEmail(email: string) {\n const user = await findUserByEmail(email);\n if (!user) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"User not found\",\n });\n }\n\n // Do NOT delete the user's sessions here — resending a verification email\n // must never log the account out of its active devices. A previously issued\n // verification token simply expires on its own; issuing a fresh one is\n // enough.\n\n const verificationToken = await createSession({\n id: uuidv4(),\n type: SessionType.EMAIL_VERIFICATION,\n userId: user.id,\n expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30),\n });\n\n try {\n await sendMail({\n from: \"hello@studious.sh\",\n to: user.email,\n subject: \"Verify your email\",\n text: `Click the link to verify your email: ${env.NEXT_PUBLIC_APP_URL}/verify/${verificationToken.id}`,\n });\n } catch (err) {\n logger.error(\"Failed to send verification email\", {\n email: user.email,\n err,\n });\n }\n\n return { success: true };\n}\n\n/** Verify email via token. Marks user as verified and deletes the session. */\nexport async function verify(token: string) {\n const session = await findSessionByIdAndType(\n token,\n SessionType.EMAIL_VERIFICATION\n );\n if (!session) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Session not found\",\n });\n }\n\n if (session.expiresAt && session.expiresAt < new Date()) {\n await deleteSessionById(token);\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Session expired\",\n });\n }\n\n if (!session.userId) {\n await deleteSessionById(token);\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Invalid session state\",\n });\n }\n\n await updateUserVerified(session.userId, true);\n await deleteSessionById(token);\n return { success: true };\n}\n\n/** Send password reset email. Silently succeeds if email not found (security). */\nexport async function requestPasswordReset(email: string) {\n const user = await findUserByEmail(email);\n if (!user) {\n return { success: true };\n }\n\n const twoHoursFromNow = new Date(Date.now() + 1000 * 60 * 60 * 2);\n await deleteShortLivedSessionsByUserId(user.id, twoHoursFromNow);\n\n const resetToken = await createSession({\n id: uuidv4(),\n type: SessionType.PASSWORD_RESET,\n userId: user.id,\n expiresAt: new Date(Date.now() + 1000 * 60 * 60),\n });\n\n try {\n await sendMail({\n from: \"hello@studious.sh\",\n to: user.email,\n subject: \"Reset your password\",\n text: `Click the link to reset your password: ${env.NEXT_PUBLIC_APP_URL}/reset-password/${resetToken.id}`,\n });\n } catch (err) {\n logger.error(\"Failed to send password reset email\", {\n email: user.email,\n err,\n });\n }\n\n return { success: true };\n}\n\n/** Reset password using token from email. Validates token and updates password. */\nexport async function resetPassword(token: string, password: string) {\n const session = await findSessionByIdWithUserAndType(\n token,\n SessionType.PASSWORD_RESET\n );\n if (!session || !session.userId) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Invalid or expired reset token\",\n });\n }\n\n if (session.expiresAt && session.expiresAt < new Date()) {\n await deleteSessionById(token);\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Reset token has expired\",\n });\n }\n\n await updateUserPassword(session.userId, await hash(password, 10));\n // Revoke every session (AUTH + any other reset/verification tokens), not\n // just the consumed reset token, so a stolen bearer token can't outlive a\n // password reset.\n await deleteSessionsByUserId(session.userId);\n return { success: true };\n}\n"],"names":[],"mappings":"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","debug_id":"50950b99-0cf5-508a-86f4-0c11cb07851e"}
@@ -1 +1 @@
1
- {"version":3,"file":"fees.d.ts","sourceRoot":"/","sources":["services/fees.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fees.d.ts","sourceRoot":"/","sources":["services/fees.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@
3
3
  * Payment gateway intentionally deferred; this is a manual ledger.
4
4
  */
5
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]="17f46206-bcf6-5721-9d78-09801ff55dea")}catch(e){}}();
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]="fbc59022-10f0-5feb-9396-4ad26a37b74a")}catch(e){}}();
7
7
  import { TRPCError } from "@trpc/server";
8
8
  import { findActiveSchoolAdminMembership } from "../models/school.js";
9
9
  import { createFeeInvoices, createFeePayment, createFeePlan, getFeeInvoice, getFeePayment, getFeePlan, listActiveStudentIds, listFeeInvoices, listFeePlans, listPlanEnrollmentIds, listStudentBalances, setFeePlanEnrollments, sumPaymentsForInvoice, updateFeePlan, updateInvoiceStatus, } from "../models/fees.js";
@@ -310,8 +310,12 @@ export async function getReceiptPdf(userId, paymentId) {
310
310
  },
311
311
  {
312
312
  label: "Paid at",
313
+ // Format in UTC rather than a hardcoded country timezone — Studious is
314
+ // used internationally, and this matches the UTC basis already used
315
+ // for invoice month labels. A per-school timezone setting is the
316
+ // proper long-term home for this (see docs/audits bug audit §2).
313
317
  value: new Intl.DateTimeFormat("en-CA", {
314
- timeZone: "Asia/Kathmandu",
318
+ timeZone: "UTC",
315
319
  year: "numeric",
316
320
  month: "2-digit",
317
321
  day: "2-digit",
@@ -327,4 +331,4 @@ export async function getReceiptPdf(userId, paymentId) {
327
331
  };
328
332
  }
329
333
  //# sourceMappingURL=fees.js.map
330
- //# debugId=17f46206-bcf6-5721-9d78-09801ff55dea
334
+ //# debugId=fbc59022-10f0-5feb-9396-4ad26a37b74a
@@ -1 +1 @@
1
- {"version":3,"file":"fees.js","sources":["services/fees.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Fee ledger service — school-admin gated monthly invoicing + payment recording.\n * Payment gateway intentionally deferred; this is a manual ledger.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { FeeInvoiceStatus, FeePaymentMethod } from \"@prisma/client\";\nimport { findActiveSchoolAdminMembership } from \"../models/school.js\";\nimport {\n createFeeInvoices,\n createFeePayment,\n createFeePlan,\n getFeeInvoice,\n getFeePayment,\n getFeePlan,\n listActiveStudentIds,\n listFeeInvoices,\n listFeePlans,\n listPlanEnrollmentIds,\n listStudentBalances,\n setFeePlanEnrollments,\n sumPaymentsForInvoice,\n updateFeePlan,\n updateInvoiceStatus,\n} from \"../models/fees.js\";\nimport {\n buildFeePdf,\n formatNpr,\n monthLabel,\n} from \"../lib/feePdf.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 fees\",\n });\n }\n return membership;\n}\n\nasync function assertActiveSchoolStudents(\n schoolId: string,\n studentIds: string[]\n) {\n const activeIds = new Set(await listActiveStudentIds(schoolId));\n const invalid = studentIds.filter((id) => !activeIds.has(id));\n if (invalid.length > 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"One or more students are not active members of this school\",\n });\n }\n}\n\nfunction assertMonth(month: number) {\n if (month < 1 || month > 12) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"month must be between 1 and 12\",\n });\n }\n}\n\nfunction invoicePaidTotal(payments: { amountNpr: number }[]): number {\n return payments.reduce((s, p) => s + p.amountNpr, 0);\n}\n\nfunction deriveStatus(\n amountNpr: number,\n paidNpr: number,\n current: FeeInvoiceStatus\n): FeeInvoiceStatus {\n if (current === \"VOID\") return \"VOID\";\n if (paidNpr <= 0) return \"UNPAID\";\n if (paidNpr >= amountNpr) return \"PAID\";\n return \"PARTIAL\";\n}\n\nfunction withBalance<\n T extends {\n amountNpr: number;\n status: FeeInvoiceStatus;\n payments: { amountNpr: number }[];\n },\n>(invoice: T) {\n const paidNpr = invoicePaidTotal(invoice.payments);\n return {\n ...invoice,\n paidNpr,\n balanceNpr: Math.max(0, invoice.amountNpr - paidNpr),\n };\n}\n\nexport async function listPlans(userId: string, schoolId: string) {\n await requireAdmin(schoolId, userId);\n const plans = await listFeePlans(schoolId);\n return { plans };\n}\n\nexport async function getPlan(userId: string, planId: string) {\n const plan = await getFeePlan(planId);\n if (!plan) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Fee plan not found\" });\n }\n await requireAdmin(plan.schoolId, userId);\n return { plan };\n}\n\nexport async function createPlan(\n userId: string,\n input: {\n schoolId: string;\n name: string;\n description?: string | null;\n amountNpr: number;\n studentIds?: string[];\n }\n) {\n await requireAdmin(input.schoolId, userId);\n if (input.amountNpr <= 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"amountNpr must be positive\",\n });\n }\n const plan = await createFeePlan({\n schoolId: input.schoolId,\n name: input.name.trim(),\n description: input.description?.trim() || null,\n amountNpr: input.amountNpr,\n });\n if (input.studentIds?.length) {\n await assertActiveSchoolStudents(input.schoolId, input.studentIds);\n const updated = await setFeePlanEnrollments(plan.id, input.studentIds);\n return { plan: updated };\n }\n return { plan };\n}\n\nexport async function updatePlan(\n userId: string,\n input: {\n planId: string;\n name?: string;\n description?: string | null;\n amountNpr?: number;\n isActive?: boolean;\n }\n) {\n const existing = await getFeePlan(input.planId);\n if (!existing) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Fee plan not found\" });\n }\n await requireAdmin(existing.schoolId, userId);\n if (input.amountNpr != null && input.amountNpr <= 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"amountNpr must be positive\",\n });\n }\n const plan = await updateFeePlan(input.planId, {\n name: input.name?.trim(),\n description: input.description,\n amountNpr: input.amountNpr,\n isActive: input.isActive,\n });\n return { plan };\n}\n\nexport async function setPlanStudents(\n userId: string,\n input: { planId: string; studentIds: string[] }\n) {\n const existing = await getFeePlan(input.planId);\n if (!existing) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Fee plan not found\" });\n }\n await requireAdmin(existing.schoolId, userId);\n await assertActiveSchoolStudents(existing.schoolId, input.studentIds);\n const plan = await setFeePlanEnrollments(input.planId, input.studentIds);\n return { plan };\n}\n\nexport async function generateMonthlyInvoices(\n userId: string,\n input: {\n schoolId: string;\n feePlanId: string;\n year: number;\n month: number;\n studentIds?: string[];\n dueDate?: Date | null;\n }\n) {\n await requireAdmin(input.schoolId, userId);\n assertMonth(input.month);\n\n const plan = await getFeePlan(input.feePlanId);\n if (!plan || plan.schoolId !== input.schoolId) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Fee plan not found\" });\n }\n if (!plan.isActive) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot generate invoices for an inactive fee plan\",\n });\n }\n\n let studentIds = input.studentIds;\n if (studentIds?.length) {\n await assertActiveSchoolStudents(input.schoolId, studentIds);\n } else {\n const enrolled = await listPlanEnrollmentIds(plan.id);\n studentIds = enrolled.length\n ? enrolled\n : await listActiveStudentIds(input.schoolId);\n }\n\n if (!studentIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"No students to invoice\",\n });\n }\n\n const result = await createFeeInvoices(\n studentIds.map((studentId) => ({\n schoolId: input.schoolId,\n feePlanId: plan.id,\n studentId,\n year: input.year,\n month: input.month,\n amountNpr: plan.amountNpr,\n currency: plan.currency,\n dueDate: input.dueDate ?? null,\n }))\n );\n\n const invoices = await listFeeInvoices({\n schoolId: input.schoolId,\n feePlanId: plan.id,\n year: input.year,\n month: input.month,\n });\n\n return {\n createdCount: result.count,\n invoices: invoices.map(withBalance),\n };\n}\n\nexport async function listInvoices(\n userId: string,\n input: {\n schoolId: string;\n year?: number;\n month?: number;\n studentId?: string;\n feePlanId?: string;\n status?: FeeInvoiceStatus;\n }\n) {\n await requireAdmin(input.schoolId, userId);\n if (input.month != null) assertMonth(input.month);\n const invoices = await listFeeInvoices(input);\n return { invoices: invoices.map(withBalance) };\n}\n\nexport async function getInvoice(userId: string, invoiceId: string) {\n const invoice = await getFeeInvoice(invoiceId);\n if (!invoice) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invoice not found\" });\n }\n await requireAdmin(invoice.schoolId, userId);\n return { invoice: withBalance(invoice) };\n}\n\nexport async function recordPayment(\n userId: string,\n input: {\n invoiceId: string;\n amountNpr: number;\n method: FeePaymentMethod;\n reference?: string | null;\n paidAt?: Date;\n notes?: string | null;\n }\n) {\n const invoice = await getFeeInvoice(input.invoiceId);\n if (!invoice) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invoice not found\" });\n }\n await requireAdmin(invoice.schoolId, userId);\n\n if (invoice.status === \"VOID\") {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot record payment on a void invoice\",\n });\n }\n if (input.amountNpr <= 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"amountNpr must be positive\",\n });\n }\n if (\n (input.method === \"BANK\" || input.method === \"ESEWA_REF\") &&\n !input.reference?.trim()\n ) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"reference is required for BANK and ESEWA_REF payments\",\n });\n }\n\n const paidSoFar = invoicePaidTotal(invoice.payments);\n const balance = Math.max(0, invoice.amountNpr - paidSoFar);\n if (input.amountNpr > balance) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: `Payment exceeds balance (${formatNpr(balance)})`,\n });\n }\n\n const payment = await createFeePayment({\n schoolId: invoice.schoolId,\n invoiceId: invoice.id,\n amountNpr: input.amountNpr,\n method: input.method,\n reference: input.reference?.trim() || null,\n paidAt: input.paidAt,\n recordedById: userId,\n notes: input.notes?.trim() || null,\n });\n\n const aggregate = await sumPaymentsForInvoice(invoice.id);\n const newPaid = aggregate._sum.amountNpr ?? 0;\n const status = deriveStatus(invoice.amountNpr, newPaid, invoice.status);\n await updateInvoiceStatus(invoice.id, status);\n\n const refreshed = await getFeeInvoice(invoice.id);\n return {\n payment,\n invoice: refreshed ? withBalance(refreshed) : null,\n };\n}\n\nexport async function listBalances(userId: string, schoolId: string) {\n await requireAdmin(schoolId, userId);\n const balances = await listStudentBalances(schoolId);\n return { balances };\n}\n\nexport async function getInvoicePdf(userId: string, invoiceId: string) {\n const invoice = await getFeeInvoice(invoiceId);\n if (!invoice) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invoice not found\" });\n }\n await requireAdmin(invoice.schoolId, userId);\n\n const paidNpr = invoicePaidTotal(invoice.payments);\n const balanceNpr = Math.max(0, invoice.amountNpr - paidNpr);\n const studentName =\n invoice.student.profile?.displayName || invoice.student.username;\n\n const bytes = await buildFeePdf({\n schoolName: invoice.school.name,\n title: \"Fee Invoice\",\n subtitle: `${invoice.feePlan.name} · ${monthLabel(invoice.year, invoice.month)}`,\n letterhead: invoice.school.letterhead\n ? {\n path: invoice.school.letterhead.path,\n type: invoice.school.letterhead.type,\n }\n : null,\n lines: [\n { label: \"Invoice ID\", value: invoice.id.slice(0, 8).toUpperCase() },\n { label: \"Student\", value: studentName },\n { label: \"Period\", value: monthLabel(invoice.year, invoice.month) },\n { label: \"Plan\", value: invoice.feePlan.name },\n { label: \"Amount\", value: formatNpr(invoice.amountNpr) },\n { label: \"Paid\", value: formatNpr(paidNpr) },\n { label: \"Balance\", value: formatNpr(balanceNpr) },\n { label: \"Status\", value: invoice.status },\n ],\n footerNote: \"Manual ledger — not a gateway receipt. Keep for school records.\",\n });\n\n return {\n fileName: `invoice-${invoice.year}-${String(invoice.month).padStart(2, \"0\")}-${invoice.id.slice(0, 8)}.pdf`,\n contentType: \"application/pdf\",\n base64: Buffer.from(bytes).toString(\"base64\"),\n };\n}\n\nexport async function getReceiptPdf(userId: string, paymentId: string) {\n const payment = await getFeePayment(paymentId);\n if (!payment) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Payment not found\" });\n }\n await requireAdmin(payment.schoolId, userId);\n\n const invoice = payment.invoice;\n const studentName =\n invoice.student.profile?.displayName || invoice.student.username;\n\n const bytes = await buildFeePdf({\n schoolName: invoice.school.name,\n title: \"Payment Receipt\",\n subtitle: `${invoice.feePlan.name} · ${monthLabel(invoice.year, invoice.month)}`,\n letterhead: invoice.school.letterhead\n ? {\n path: invoice.school.letterhead.path,\n type: invoice.school.letterhead.type,\n }\n : null,\n lines: [\n { label: \"Receipt ID\", value: payment.id.slice(0, 8).toUpperCase() },\n { label: \"Student\", value: studentName },\n { label: \"Period\", value: monthLabel(invoice.year, invoice.month) },\n { label: \"Amount paid\", value: formatNpr(payment.amountNpr) },\n { label: \"Method\", value: payment.method },\n {\n label: \"Reference\",\n value: payment.reference?.trim() || \"—\",\n },\n {\n label: \"Paid at\",\n value: new Intl.DateTimeFormat(\"en-CA\", {\n timeZone: \"Asia/Kathmandu\",\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).format(payment.paidAt),\n },\n ],\n footerNote: \"Manual ledger receipt — gateway integration deferred.\",\n });\n\n return {\n fileName: `receipt-${payment.id.slice(0, 8)}.pdf`,\n contentType: \"application/pdf\",\n base64: Buffer.from(bytes).toString(\"base64\"),\n };\n}\n"],"names":[],"mappings":"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","debug_id":"17f46206-bcf6-5721-9d78-09801ff55dea"}
1
+ {"version":3,"file":"fees.js","sources":["services/fees.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Fee ledger service — school-admin gated monthly invoicing + payment recording.\n * Payment gateway intentionally deferred; this is a manual ledger.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { FeeInvoiceStatus, FeePaymentMethod } from \"@prisma/client\";\nimport { findActiveSchoolAdminMembership } from \"../models/school.js\";\nimport {\n createFeeInvoices,\n createFeePayment,\n createFeePlan,\n getFeeInvoice,\n getFeePayment,\n getFeePlan,\n listActiveStudentIds,\n listFeeInvoices,\n listFeePlans,\n listPlanEnrollmentIds,\n listStudentBalances,\n setFeePlanEnrollments,\n sumPaymentsForInvoice,\n updateFeePlan,\n updateInvoiceStatus,\n} from \"../models/fees.js\";\nimport {\n buildFeePdf,\n formatNpr,\n monthLabel,\n} from \"../lib/feePdf.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 fees\",\n });\n }\n return membership;\n}\n\nasync function assertActiveSchoolStudents(\n schoolId: string,\n studentIds: string[]\n) {\n const activeIds = new Set(await listActiveStudentIds(schoolId));\n const invalid = studentIds.filter((id) => !activeIds.has(id));\n if (invalid.length > 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"One or more students are not active members of this school\",\n });\n }\n}\n\nfunction assertMonth(month: number) {\n if (month < 1 || month > 12) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"month must be between 1 and 12\",\n });\n }\n}\n\nfunction invoicePaidTotal(payments: { amountNpr: number }[]): number {\n return payments.reduce((s, p) => s + p.amountNpr, 0);\n}\n\nfunction deriveStatus(\n amountNpr: number,\n paidNpr: number,\n current: FeeInvoiceStatus\n): FeeInvoiceStatus {\n if (current === \"VOID\") return \"VOID\";\n if (paidNpr <= 0) return \"UNPAID\";\n if (paidNpr >= amountNpr) return \"PAID\";\n return \"PARTIAL\";\n}\n\nfunction withBalance<\n T extends {\n amountNpr: number;\n status: FeeInvoiceStatus;\n payments: { amountNpr: number }[];\n },\n>(invoice: T) {\n const paidNpr = invoicePaidTotal(invoice.payments);\n return {\n ...invoice,\n paidNpr,\n balanceNpr: Math.max(0, invoice.amountNpr - paidNpr),\n };\n}\n\nexport async function listPlans(userId: string, schoolId: string) {\n await requireAdmin(schoolId, userId);\n const plans = await listFeePlans(schoolId);\n return { plans };\n}\n\nexport async function getPlan(userId: string, planId: string) {\n const plan = await getFeePlan(planId);\n if (!plan) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Fee plan not found\" });\n }\n await requireAdmin(plan.schoolId, userId);\n return { plan };\n}\n\nexport async function createPlan(\n userId: string,\n input: {\n schoolId: string;\n name: string;\n description?: string | null;\n amountNpr: number;\n studentIds?: string[];\n }\n) {\n await requireAdmin(input.schoolId, userId);\n if (input.amountNpr <= 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"amountNpr must be positive\",\n });\n }\n const plan = await createFeePlan({\n schoolId: input.schoolId,\n name: input.name.trim(),\n description: input.description?.trim() || null,\n amountNpr: input.amountNpr,\n });\n if (input.studentIds?.length) {\n await assertActiveSchoolStudents(input.schoolId, input.studentIds);\n const updated = await setFeePlanEnrollments(plan.id, input.studentIds);\n return { plan: updated };\n }\n return { plan };\n}\n\nexport async function updatePlan(\n userId: string,\n input: {\n planId: string;\n name?: string;\n description?: string | null;\n amountNpr?: number;\n isActive?: boolean;\n }\n) {\n const existing = await getFeePlan(input.planId);\n if (!existing) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Fee plan not found\" });\n }\n await requireAdmin(existing.schoolId, userId);\n if (input.amountNpr != null && input.amountNpr <= 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"amountNpr must be positive\",\n });\n }\n const plan = await updateFeePlan(input.planId, {\n name: input.name?.trim(),\n description: input.description,\n amountNpr: input.amountNpr,\n isActive: input.isActive,\n });\n return { plan };\n}\n\nexport async function setPlanStudents(\n userId: string,\n input: { planId: string; studentIds: string[] }\n) {\n const existing = await getFeePlan(input.planId);\n if (!existing) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Fee plan not found\" });\n }\n await requireAdmin(existing.schoolId, userId);\n await assertActiveSchoolStudents(existing.schoolId, input.studentIds);\n const plan = await setFeePlanEnrollments(input.planId, input.studentIds);\n return { plan };\n}\n\nexport async function generateMonthlyInvoices(\n userId: string,\n input: {\n schoolId: string;\n feePlanId: string;\n year: number;\n month: number;\n studentIds?: string[];\n dueDate?: Date | null;\n }\n) {\n await requireAdmin(input.schoolId, userId);\n assertMonth(input.month);\n\n const plan = await getFeePlan(input.feePlanId);\n if (!plan || plan.schoolId !== input.schoolId) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Fee plan not found\" });\n }\n if (!plan.isActive) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot generate invoices for an inactive fee plan\",\n });\n }\n\n let studentIds = input.studentIds;\n if (studentIds?.length) {\n await assertActiveSchoolStudents(input.schoolId, studentIds);\n } else {\n const enrolled = await listPlanEnrollmentIds(plan.id);\n studentIds = enrolled.length\n ? enrolled\n : await listActiveStudentIds(input.schoolId);\n }\n\n if (!studentIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"No students to invoice\",\n });\n }\n\n const result = await createFeeInvoices(\n studentIds.map((studentId) => ({\n schoolId: input.schoolId,\n feePlanId: plan.id,\n studentId,\n year: input.year,\n month: input.month,\n amountNpr: plan.amountNpr,\n currency: plan.currency,\n dueDate: input.dueDate ?? null,\n }))\n );\n\n const invoices = await listFeeInvoices({\n schoolId: input.schoolId,\n feePlanId: plan.id,\n year: input.year,\n month: input.month,\n });\n\n return {\n createdCount: result.count,\n invoices: invoices.map(withBalance),\n };\n}\n\nexport async function listInvoices(\n userId: string,\n input: {\n schoolId: string;\n year?: number;\n month?: number;\n studentId?: string;\n feePlanId?: string;\n status?: FeeInvoiceStatus;\n }\n) {\n await requireAdmin(input.schoolId, userId);\n if (input.month != null) assertMonth(input.month);\n const invoices = await listFeeInvoices(input);\n return { invoices: invoices.map(withBalance) };\n}\n\nexport async function getInvoice(userId: string, invoiceId: string) {\n const invoice = await getFeeInvoice(invoiceId);\n if (!invoice) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invoice not found\" });\n }\n await requireAdmin(invoice.schoolId, userId);\n return { invoice: withBalance(invoice) };\n}\n\nexport async function recordPayment(\n userId: string,\n input: {\n invoiceId: string;\n amountNpr: number;\n method: FeePaymentMethod;\n reference?: string | null;\n paidAt?: Date;\n notes?: string | null;\n }\n) {\n const invoice = await getFeeInvoice(input.invoiceId);\n if (!invoice) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invoice not found\" });\n }\n await requireAdmin(invoice.schoolId, userId);\n\n if (invoice.status === \"VOID\") {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot record payment on a void invoice\",\n });\n }\n if (input.amountNpr <= 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"amountNpr must be positive\",\n });\n }\n if (\n (input.method === \"BANK\" || input.method === \"ESEWA_REF\") &&\n !input.reference?.trim()\n ) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"reference is required for BANK and ESEWA_REF payments\",\n });\n }\n\n const paidSoFar = invoicePaidTotal(invoice.payments);\n const balance = Math.max(0, invoice.amountNpr - paidSoFar);\n if (input.amountNpr > balance) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: `Payment exceeds balance (${formatNpr(balance)})`,\n });\n }\n\n const payment = await createFeePayment({\n schoolId: invoice.schoolId,\n invoiceId: invoice.id,\n amountNpr: input.amountNpr,\n method: input.method,\n reference: input.reference?.trim() || null,\n paidAt: input.paidAt,\n recordedById: userId,\n notes: input.notes?.trim() || null,\n });\n\n const aggregate = await sumPaymentsForInvoice(invoice.id);\n const newPaid = aggregate._sum.amountNpr ?? 0;\n const status = deriveStatus(invoice.amountNpr, newPaid, invoice.status);\n await updateInvoiceStatus(invoice.id, status);\n\n const refreshed = await getFeeInvoice(invoice.id);\n return {\n payment,\n invoice: refreshed ? withBalance(refreshed) : null,\n };\n}\n\nexport async function listBalances(userId: string, schoolId: string) {\n await requireAdmin(schoolId, userId);\n const balances = await listStudentBalances(schoolId);\n return { balances };\n}\n\nexport async function getInvoicePdf(userId: string, invoiceId: string) {\n const invoice = await getFeeInvoice(invoiceId);\n if (!invoice) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invoice not found\" });\n }\n await requireAdmin(invoice.schoolId, userId);\n\n const paidNpr = invoicePaidTotal(invoice.payments);\n const balanceNpr = Math.max(0, invoice.amountNpr - paidNpr);\n const studentName =\n invoice.student.profile?.displayName || invoice.student.username;\n\n const bytes = await buildFeePdf({\n schoolName: invoice.school.name,\n title: \"Fee Invoice\",\n subtitle: `${invoice.feePlan.name} · ${monthLabel(invoice.year, invoice.month)}`,\n letterhead: invoice.school.letterhead\n ? {\n path: invoice.school.letterhead.path,\n type: invoice.school.letterhead.type,\n }\n : null,\n lines: [\n { label: \"Invoice ID\", value: invoice.id.slice(0, 8).toUpperCase() },\n { label: \"Student\", value: studentName },\n { label: \"Period\", value: monthLabel(invoice.year, invoice.month) },\n { label: \"Plan\", value: invoice.feePlan.name },\n { label: \"Amount\", value: formatNpr(invoice.amountNpr) },\n { label: \"Paid\", value: formatNpr(paidNpr) },\n { label: \"Balance\", value: formatNpr(balanceNpr) },\n { label: \"Status\", value: invoice.status },\n ],\n footerNote: \"Manual ledger — not a gateway receipt. Keep for school records.\",\n });\n\n return {\n fileName: `invoice-${invoice.year}-${String(invoice.month).padStart(2, \"0\")}-${invoice.id.slice(0, 8)}.pdf`,\n contentType: \"application/pdf\",\n base64: Buffer.from(bytes).toString(\"base64\"),\n };\n}\n\nexport async function getReceiptPdf(userId: string, paymentId: string) {\n const payment = await getFeePayment(paymentId);\n if (!payment) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Payment not found\" });\n }\n await requireAdmin(payment.schoolId, userId);\n\n const invoice = payment.invoice;\n const studentName =\n invoice.student.profile?.displayName || invoice.student.username;\n\n const bytes = await buildFeePdf({\n schoolName: invoice.school.name,\n title: \"Payment Receipt\",\n subtitle: `${invoice.feePlan.name} · ${monthLabel(invoice.year, invoice.month)}`,\n letterhead: invoice.school.letterhead\n ? {\n path: invoice.school.letterhead.path,\n type: invoice.school.letterhead.type,\n }\n : null,\n lines: [\n { label: \"Receipt ID\", value: payment.id.slice(0, 8).toUpperCase() },\n { label: \"Student\", value: studentName },\n { label: \"Period\", value: monthLabel(invoice.year, invoice.month) },\n { label: \"Amount paid\", value: formatNpr(payment.amountNpr) },\n { label: \"Method\", value: payment.method },\n {\n label: \"Reference\",\n value: payment.reference?.trim() || \"—\",\n },\n {\n label: \"Paid at\",\n // Format in UTC rather than a hardcoded country timezone — Studious is\n // used internationally, and this matches the UTC basis already used\n // for invoice month labels. A per-school timezone setting is the\n // proper long-term home for this (see docs/audits bug audit §2).\n value: new Intl.DateTimeFormat(\"en-CA\", {\n timeZone: \"UTC\",\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).format(payment.paidAt),\n },\n ],\n footerNote: \"Manual ledger receipt — gateway integration deferred.\",\n });\n\n return {\n fileName: `receipt-${payment.id.slice(0, 8)}.pdf`,\n contentType: \"application/pdf\",\n base64: Buffer.from(bytes).toString(\"base64\"),\n };\n}\n"],"names":[],"mappings":"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","debug_id":"fbc59022-10f0-5feb-9396-4ad26a37b74a"}
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"/","sources":["services/module.ts"],"names":[],"mappings":"AASA,OAAO,EACL,oBAAoB,IAAI,cAAc,EACtC,yBAAyB,EAG1B,MAAM,gBAAgB,CAAC;AA4BxB,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;MAGtE;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;GAkB/F;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDF;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;;GASvC;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;MAuBjD;AA+FD,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;;;;;;;;;;;GA+BF;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,yBAAyB,GAAG,IAAI,CAAC;IACnD,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;;;;;;;;;;;GAqCF;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE;;GASvC;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;MAoCtE;AAkBD;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmH9E;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;;;;;;;;GAqB3C;AAMD;;;GAGG;AACH,wBAAsB,4BAA4B,CAChD,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,iBAyCrB;AAED;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,iBAsCpB"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"/","sources":["services/module.ts"],"names":[],"mappings":"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"}