@goscribe/server 1.2.0 → 1.3.1
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.
- package/check-difficulty.cjs +14 -0
- package/check-questions.cjs +14 -0
- package/db-summary.cjs +22 -0
- package/dist/context.d.ts +5 -1
- package/dist/lib/activity_human_description.d.ts +13 -0
- package/dist/lib/activity_human_description.js +221 -0
- package/dist/lib/activity_human_description.test.d.ts +1 -0
- package/dist/lib/activity_human_description.test.js +16 -0
- package/dist/lib/activity_log_service.d.ts +87 -0
- package/dist/lib/activity_log_service.js +276 -0
- package/dist/lib/activity_log_service.test.d.ts +1 -0
- package/dist/lib/activity_log_service.test.js +27 -0
- package/dist/lib/ai-session.d.ts +15 -2
- package/dist/lib/ai-session.js +147 -85
- package/dist/lib/constants.d.ts +13 -0
- package/dist/lib/constants.js +12 -0
- package/dist/lib/email.d.ts +11 -0
- package/dist/lib/email.js +193 -0
- package/dist/lib/env.d.ts +13 -0
- package/dist/lib/env.js +16 -0
- package/dist/lib/inference.d.ts +4 -1
- package/dist/lib/inference.js +3 -3
- package/dist/lib/logger.d.ts +4 -4
- package/dist/lib/logger.js +30 -8
- package/dist/lib/notification-service.d.ts +152 -0
- package/dist/lib/notification-service.js +473 -0
- package/dist/lib/notification-service.test.d.ts +1 -0
- package/dist/lib/notification-service.test.js +87 -0
- package/dist/lib/prisma.d.ts +2 -1
- package/dist/lib/prisma.js +5 -1
- package/dist/lib/pusher.d.ts +23 -0
- package/dist/lib/pusher.js +69 -5
- package/dist/lib/retry.d.ts +15 -0
- package/dist/lib/retry.js +37 -0
- package/dist/lib/storage.js +2 -2
- package/dist/lib/stripe.d.ts +9 -0
- package/dist/lib/stripe.js +36 -0
- package/dist/lib/subscription_service.d.ts +37 -0
- package/dist/lib/subscription_service.js +654 -0
- package/dist/lib/usage_service.d.ts +26 -0
- package/dist/lib/usage_service.js +59 -0
- package/dist/lib/worksheet-generation.d.ts +91 -0
- package/dist/lib/worksheet-generation.js +95 -0
- package/dist/lib/worksheet-generation.test.d.ts +1 -0
- package/dist/lib/worksheet-generation.test.js +20 -0
- package/dist/lib/workspace-access.d.ts +18 -0
- package/dist/lib/workspace-access.js +13 -0
- package/dist/routers/_app.d.ts +1349 -253
- package/dist/routers/_app.js +10 -0
- package/dist/routers/admin.d.ts +361 -0
- package/dist/routers/admin.js +633 -0
- package/dist/routers/annotations.d.ts +219 -0
- package/dist/routers/annotations.js +187 -0
- package/dist/routers/auth.d.ts +88 -7
- package/dist/routers/auth.js +339 -19
- package/dist/routers/chat.d.ts +6 -12
- package/dist/routers/copilot.d.ts +199 -0
- package/dist/routers/copilot.js +571 -0
- package/dist/routers/flashcards.d.ts +47 -81
- package/dist/routers/flashcards.js +143 -27
- package/dist/routers/members.d.ts +36 -7
- package/dist/routers/members.js +200 -19
- package/dist/routers/notifications.d.ts +99 -0
- package/dist/routers/notifications.js +127 -0
- package/dist/routers/payment.d.ts +89 -0
- package/dist/routers/payment.js +403 -0
- package/dist/routers/podcast.d.ts +8 -13
- package/dist/routers/podcast.js +54 -31
- package/dist/routers/studyguide.d.ts +1 -29
- package/dist/routers/studyguide.js +80 -71
- package/dist/routers/worksheets.d.ts +105 -38
- package/dist/routers/worksheets.js +258 -68
- package/dist/routers/workspace.d.ts +139 -60
- package/dist/routers/workspace.js +455 -315
- package/dist/scripts/purge-deleted-users.d.ts +1 -0
- package/dist/scripts/purge-deleted-users.js +149 -0
- package/dist/server.js +130 -10
- package/dist/services/flashcard-progress.service.d.ts +18 -66
- package/dist/services/flashcard-progress.service.js +51 -42
- package/dist/trpc.d.ts +20 -21
- package/dist/trpc.js +150 -1
- package/mcq-test.cjs +36 -0
- package/package.json +9 -2
- package/prisma/migrations/20260413143206_init/migration.sql +873 -0
- package/prisma/schema.prisma +471 -324
- package/src/context.ts +4 -1
- package/src/lib/activity_human_description.test.ts +28 -0
- package/src/lib/activity_human_description.ts +239 -0
- package/src/lib/activity_log_service.test.ts +37 -0
- package/src/lib/activity_log_service.ts +353 -0
- package/src/lib/ai-session.ts +79 -51
- package/src/lib/email.ts +213 -29
- package/src/lib/env.ts +23 -6
- package/src/lib/inference.ts +2 -2
- package/src/lib/notification-service.test.ts +106 -0
- package/src/lib/notification-service.ts +677 -0
- package/src/lib/prisma.ts +6 -1
- package/src/lib/pusher.ts +86 -2
- package/src/lib/stripe.ts +39 -0
- package/src/lib/subscription_service.ts +722 -0
- package/src/lib/usage_service.ts +74 -0
- package/src/lib/worksheet-generation.test.ts +31 -0
- package/src/lib/worksheet-generation.ts +139 -0
- package/src/routers/_app.ts +9 -0
- package/src/routers/admin.ts +710 -0
- package/src/routers/annotations.ts +41 -0
- package/src/routers/auth.ts +338 -28
- package/src/routers/copilot.ts +719 -0
- package/src/routers/flashcards.ts +201 -68
- package/src/routers/members.ts +280 -80
- package/src/routers/notifications.ts +142 -0
- package/src/routers/payment.ts +448 -0
- package/src/routers/podcast.ts +112 -83
- package/src/routers/studyguide.ts +12 -0
- package/src/routers/worksheets.ts +289 -66
- package/src/routers/workspace.ts +329 -122
- package/src/scripts/purge-deleted-users.ts +167 -0
- package/src/server.ts +137 -11
- package/src/services/flashcard-progress.service.ts +49 -37
- package/src/trpc.ts +184 -5
- package/test-generate.js +30 -0
- package/test-ratio.cjs +9 -0
- package/zod-test.cjs +22 -0
- package/prisma/migrations/20250826124819_add_worksheet_difficulty_and_estimated_time/migration.sql +0 -213
- package/prisma/migrations/20250826133236_add_worksheet_question_progress/migration.sql +0 -31
- package/prisma/seed.mjs +0 -135
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { PrismaClient } = require('@prisma/client');
|
|
2
|
+
const prisma = new PrismaClient();
|
|
3
|
+
|
|
4
|
+
async function check() {
|
|
5
|
+
const artifacts = await prisma.artifact.findMany({
|
|
6
|
+
where: { type: 'WORKSHEET' },
|
|
7
|
+
orderBy: { createdAt: 'desc' },
|
|
8
|
+
take: 5,
|
|
9
|
+
select: { id: true, title: true, difficulty: true, generatingMetadata: true }
|
|
10
|
+
});
|
|
11
|
+
console.log("Recent Worksheets:", JSON.stringify(artifacts, null, 2));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
check().catch(console.error).finally(() => prisma.$disconnect());
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { PrismaClient } = require('@prisma/client');
|
|
2
|
+
const prisma = new PrismaClient();
|
|
3
|
+
|
|
4
|
+
async function check() {
|
|
5
|
+
const artifacts = await prisma.artifact.findMany({
|
|
6
|
+
where: { type: 'WORKSHEET' },
|
|
7
|
+
orderBy: { createdAt: 'desc' },
|
|
8
|
+
take: 3,
|
|
9
|
+
select: { id: true, title: true, generatingMetadata: true, createdAt: true, questions: true }
|
|
10
|
+
});
|
|
11
|
+
console.log("Recent Worksheets:", JSON.stringify(artifacts, null, 2));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
check().catch(console.error).finally(() => prisma.$disconnect());
|
package/db-summary.cjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const { PrismaClient } = require('@prisma/client');
|
|
2
|
+
const prisma = new PrismaClient();
|
|
3
|
+
|
|
4
|
+
async function summary() {
|
|
5
|
+
const artifacts = await prisma.artifact.findMany({
|
|
6
|
+
where: { type: 'WORKSHEET' },
|
|
7
|
+
orderBy: { createdAt: 'desc' },
|
|
8
|
+
take: 10,
|
|
9
|
+
select: { id: true, createdAt: true, generatingMetadata: true, questions: { select: { type: true } } }
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
for (const a of artifacts) {
|
|
13
|
+
let mcqs = 0; let texts = 0;
|
|
14
|
+
for (const q of a.questions) {
|
|
15
|
+
if (q.type === 'MULTIPLE_CHOICE') mcqs++;
|
|
16
|
+
else if (q.type === 'TEXT') texts++;
|
|
17
|
+
}
|
|
18
|
+
console.log(`Worksheet ${a.id} at ${a.createdAt.toISOString()}: ${mcqs} MCQs, ${texts} Texts`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
summary().catch(console.error).finally(() => prisma.$disconnect());
|
package/dist/context.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CreateExpressContextOptions } from "@trpc/server/adapters/express";
|
|
2
|
+
import { prisma } from "./lib/prisma.js";
|
|
2
3
|
export declare function createContext({ req, res }: CreateExpressContextOptions): Promise<{
|
|
3
4
|
db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
|
|
4
5
|
session: any;
|
|
@@ -6,4 +7,7 @@ export declare function createContext({ req, res }: CreateExpressContextOptions)
|
|
|
6
7
|
res: import("express").Response<any, Record<string, any>>;
|
|
7
8
|
cookies: Record<string, string | undefined>;
|
|
8
9
|
}>;
|
|
9
|
-
|
|
10
|
+
/** Use `typeof prisma` for `db` so TS keeps generated model delegates (not a bare PrismaClient). */
|
|
11
|
+
export type Context = Omit<Awaited<ReturnType<typeof createContext>>, "db"> & {
|
|
12
|
+
db: typeof prisma;
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-facing labels for tRPC paths shown in activity logs.
|
|
3
|
+
* Unknown paths get a best-effort title from the procedure name.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Paths whose curated description contains `query` (case-insensitive).
|
|
7
|
+
* Used so activity log search matches human-readable text, not only raw paths.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getTrpcPathsMatchingDescriptionSearch(query: string): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Stable, human-readable line for UI and CSV exports.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getActivityHumanDescription(trpcPath: string | null | undefined, actionFallback?: string | null): string;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-facing labels for tRPC paths shown in activity logs.
|
|
3
|
+
* Unknown paths get a best-effort title from the procedure name.
|
|
4
|
+
*/
|
|
5
|
+
const PATH_LABELS = {
|
|
6
|
+
// Admin
|
|
7
|
+
"admin.getSystemStats": "Viewed system dashboard statistics",
|
|
8
|
+
"admin.listUsers": "Listed users (admin)",
|
|
9
|
+
"admin.listWorkspaces": "Listed workspaces (admin)",
|
|
10
|
+
"admin.updateUserRole": "Changed a user’s role",
|
|
11
|
+
"admin.listPlans": "Listed subscription plans",
|
|
12
|
+
"admin.upsertPlan": "Created or updated a plan",
|
|
13
|
+
"admin.deletePlan": "Deleted or deactivated a plan",
|
|
14
|
+
"admin.getUserInvoices": "Viewed a user’s invoices",
|
|
15
|
+
"admin.getUserDetailedInfo": "Viewed a user’s profile (admin)",
|
|
16
|
+
"admin.debugInvoices": "Ran invoice debug (admin)",
|
|
17
|
+
"admin.listResourcePrices": "Listed resource prices",
|
|
18
|
+
"admin.upsertResourcePrice": "Updated a resource price",
|
|
19
|
+
"admin.listRecentInvoices": "Viewed recent invoices",
|
|
20
|
+
"admin.activityList": "Viewed activity logs (admin)",
|
|
21
|
+
"admin.activityExportCsv": "Exported activity logs to CSV",
|
|
22
|
+
"admin.activityPurgeRetention": "Purged old activity logs (retention)",
|
|
23
|
+
// Workspace
|
|
24
|
+
"workspace.list": "Listed your workspaces",
|
|
25
|
+
"workspace.getTree": "Loaded folders and workspace tree",
|
|
26
|
+
"workspace.create": "Created a workspace",
|
|
27
|
+
"workspace.createFolder": "Created a folder",
|
|
28
|
+
"workspace.updateFolder": "Updated a folder",
|
|
29
|
+
"workspace.deleteFolder": "Deleted a folder",
|
|
30
|
+
"workspace.get": "Opened a workspace",
|
|
31
|
+
"workspace.getStats": "Loaded storage and usage stats",
|
|
32
|
+
"workspace.getStudyAnalytics": "Loaded study streak and analytics",
|
|
33
|
+
"workspace.update": "Updated workspace settings",
|
|
34
|
+
"workspace.delete": "Deleted a workspace",
|
|
35
|
+
"workspace.getFolderInformation": "Loaded folder details",
|
|
36
|
+
"workspace.getSharedWith": "Loaded who a workspace is shared with",
|
|
37
|
+
"workspace.uploadFiles": "Uploaded files",
|
|
38
|
+
"workspace.deleteFiles": "Deleted files",
|
|
39
|
+
"workspace.getFileUploadUrl": "Requested an upload URL",
|
|
40
|
+
"workspace.uploadAndAnalyzeMedia": "Uploaded and analyzed media",
|
|
41
|
+
"workspace.search": "Searched workspaces and content",
|
|
42
|
+
// Payment
|
|
43
|
+
"payment.getPlans": "Viewed available plans",
|
|
44
|
+
"payment.createCheckoutSession": "Started checkout",
|
|
45
|
+
"payment.confirmCheckoutSuccess": "Confirmed checkout",
|
|
46
|
+
"payment.createResourcePurchaseSession": "Started a resource purchase",
|
|
47
|
+
"payment.getUsageOverview": "Viewed plan usage and limits",
|
|
48
|
+
"payment.getResourcePrices": "Viewed resource prices",
|
|
49
|
+
// Notifications
|
|
50
|
+
"notifications.list": "Loaded notifications",
|
|
51
|
+
"notifications.unreadCount": "Checked unread notifications",
|
|
52
|
+
"notifications.markRead": "Marked a notification as read",
|
|
53
|
+
"notifications.markManyRead": "Marked notifications as read",
|
|
54
|
+
"notifications.markAllRead": "Marked all notifications as read",
|
|
55
|
+
"notifications.delete": "Deleted a notification",
|
|
56
|
+
// Members
|
|
57
|
+
"members.getMembers": "Listed workspace members",
|
|
58
|
+
"members.getCurrentUserRole": "Checked your role in a workspace",
|
|
59
|
+
"members.inviteMember": "Sent a workspace invite",
|
|
60
|
+
"members.getInvitations": "Listed pending invites",
|
|
61
|
+
"members.acceptInvite": "Accepted a workspace invite",
|
|
62
|
+
"members.changeMemberRole": "Changed a member’s role",
|
|
63
|
+
"members.removeMember": "Removed a workspace member",
|
|
64
|
+
"members.getPendingInvitations": "Listed invitations",
|
|
65
|
+
"members.cancelInvitation": "Canceled an invitation",
|
|
66
|
+
"members.resendInvitation": "Resent an invitation",
|
|
67
|
+
"members.getAllInvitationsDebug": "Listed invitations (debug)",
|
|
68
|
+
"member.acceptInvite": "Accepted a workspace invite",
|
|
69
|
+
// Flashcards
|
|
70
|
+
"flashcards.listSets": "Listed flashcard sets",
|
|
71
|
+
"flashcards.listCards": "Listed flashcards",
|
|
72
|
+
"flashcards.isGenerating": "Checked flashcard generation status",
|
|
73
|
+
"flashcards.createCard": "Created a flashcard",
|
|
74
|
+
"flashcards.updateCard": "Updated a flashcard",
|
|
75
|
+
"flashcards.gradeTypedAnswer": "Graded a flashcard answer",
|
|
76
|
+
"flashcards.deleteCard": "Deleted a flashcard",
|
|
77
|
+
"flashcards.deleteSet": "Deleted a flashcard set",
|
|
78
|
+
"flashcards.generateFromPrompt": "Generated flashcards from a prompt",
|
|
79
|
+
"flashcards.recordStudyAttempt": "Recorded a flashcard review",
|
|
80
|
+
"flashcards.getSetProgress": "Viewed flashcard progress",
|
|
81
|
+
"flashcards.getDueFlashcards": "Loaded due flashcards",
|
|
82
|
+
"flashcards.getSetStatistics": "Viewed flashcard statistics",
|
|
83
|
+
"flashcards.resetProgress": "Reset flashcard progress",
|
|
84
|
+
"flashcards.recordStudySession": "Recorded a study session",
|
|
85
|
+
// Worksheets
|
|
86
|
+
"worksheets.list": "Listed worksheets",
|
|
87
|
+
"worksheets.listPresets": "Listed worksheet presets",
|
|
88
|
+
"worksheets.createPreset": "Created a worksheet preset",
|
|
89
|
+
"worksheets.updatePreset": "Updated a worksheet preset",
|
|
90
|
+
"worksheets.deletePreset": "Deleted a worksheet preset",
|
|
91
|
+
"worksheets.create": "Created a worksheet",
|
|
92
|
+
"worksheets.get": "Opened a worksheet",
|
|
93
|
+
"worksheets.createWorksheetQuestion": "Added a worksheet question",
|
|
94
|
+
"worksheets.updateWorksheetQuestion": "Updated a worksheet question",
|
|
95
|
+
"worksheets.deleteWorksheetQuestion": "Deleted a worksheet question",
|
|
96
|
+
"worksheets.updateProblemStatus": "Updated worksheet problem status",
|
|
97
|
+
"worksheets.getProgress": "Viewed worksheet progress",
|
|
98
|
+
"worksheets.update": "Updated a worksheet",
|
|
99
|
+
"worksheets.delete": "Deleted a worksheet",
|
|
100
|
+
"worksheets.generateFromPrompt": "Generated a worksheet from a prompt",
|
|
101
|
+
"worksheets.checkAnswer": "Checked a worksheet answer",
|
|
102
|
+
// Podcast
|
|
103
|
+
"podcast.listEpisodes": "Listed podcast episodes",
|
|
104
|
+
"podcast.getEpisode": "Opened a podcast episode",
|
|
105
|
+
"podcast.generateEpisode": "Generated a podcast episode",
|
|
106
|
+
"podcast.deleteSegment": "Deleted a podcast segment",
|
|
107
|
+
"podcast.getEpisodeSchema": "Loaded podcast episode schema",
|
|
108
|
+
"podcast.updateEpisode": "Updated a podcast episode",
|
|
109
|
+
"podcast.deleteEpisode": "Deleted a podcast episode",
|
|
110
|
+
"podcast.getSegment": "Opened a podcast segment",
|
|
111
|
+
"podcast.getAvailableVoices": "Listed podcast voices",
|
|
112
|
+
// Study guide
|
|
113
|
+
"studyguide.get": "Opened or loaded a study guide",
|
|
114
|
+
// Chat
|
|
115
|
+
"chat.getChannels": "Listed chat channels",
|
|
116
|
+
"chat.getChannel": "Opened a chat channel",
|
|
117
|
+
"chat.removeChannel": "Removed a chat channel",
|
|
118
|
+
"chat.editChannel": "Renamed a chat channel",
|
|
119
|
+
"chat.createChannel": "Created a chat channel",
|
|
120
|
+
"chat.postMessage": "Sent a chat message",
|
|
121
|
+
"chat.editMessage": "Edited a chat message",
|
|
122
|
+
"chat.deleteMessage": "Deleted a chat message",
|
|
123
|
+
// Annotations
|
|
124
|
+
"annotations.listHighlights": "Listed study guide highlights",
|
|
125
|
+
"annotations.createHighlight": "Created a highlight",
|
|
126
|
+
"annotations.deleteHighlight": "Deleted a highlight",
|
|
127
|
+
"annotations.addComment": "Added a comment",
|
|
128
|
+
"annotations.updateComment": "Updated a comment",
|
|
129
|
+
"annotations.deleteComment": "Deleted a comment",
|
|
130
|
+
// Copilot
|
|
131
|
+
"copilot.listConversations": "Listed Copilot chats",
|
|
132
|
+
"copilot.getConversation": "Opened a Copilot chat",
|
|
133
|
+
"copilot.createConversation": "Started a Copilot chat",
|
|
134
|
+
"copilot.deleteConversation": "Deleted a Copilot chat",
|
|
135
|
+
"copilot.ask": "Sent a Copilot message",
|
|
136
|
+
"copilot.explainSelection": "Asked Copilot to explain a selection",
|
|
137
|
+
"copilot.suggestHighlights": "Asked Copilot for highlight suggestions",
|
|
138
|
+
"copilot.generateFlashcards": "Generated flashcards with Copilot",
|
|
139
|
+
// Auth (if ever logged)
|
|
140
|
+
"auth.updateProfile": "Updated profile",
|
|
141
|
+
"auth.uploadProfilePicture": "Uploaded a profile picture",
|
|
142
|
+
"auth.confirmProfileUpdate": "Confirmed profile update",
|
|
143
|
+
"auth.signup": "Created an account",
|
|
144
|
+
"auth.verifyEmail": "Verified email",
|
|
145
|
+
"auth.resendVerification": "Resent verification email",
|
|
146
|
+
"auth.login": "Signed in",
|
|
147
|
+
"auth.getSession": "Loaded session",
|
|
148
|
+
"auth.requestAccountDeletion": "Requested account deletion",
|
|
149
|
+
"auth.restoreAccount": "Restored account",
|
|
150
|
+
"auth.logout": "Signed out",
|
|
151
|
+
};
|
|
152
|
+
function splitCamelCase(s) {
|
|
153
|
+
const spaced = s.replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
154
|
+
return spaced.replace(/^\w/, (c) => c.toUpperCase());
|
|
155
|
+
}
|
|
156
|
+
const ROUTER_PREFIX = {
|
|
157
|
+
workspace: "Workspace",
|
|
158
|
+
payment: "Billing",
|
|
159
|
+
notifications: "Notifications",
|
|
160
|
+
admin: "Admin",
|
|
161
|
+
auth: "Account",
|
|
162
|
+
flashcards: "Flashcards",
|
|
163
|
+
worksheets: "Worksheets",
|
|
164
|
+
podcast: "Podcast",
|
|
165
|
+
studyguide: "Study guide",
|
|
166
|
+
chat: "Chat",
|
|
167
|
+
annotations: "Annotations",
|
|
168
|
+
copilot: "Copilot",
|
|
169
|
+
members: "Members",
|
|
170
|
+
};
|
|
171
|
+
function titleCaseRouter(router) {
|
|
172
|
+
return ROUTER_PREFIX[router] ?? splitCamelCase(router);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Paths whose curated description contains `query` (case-insensitive).
|
|
176
|
+
* Used so activity log search matches human-readable text, not only raw paths.
|
|
177
|
+
*/
|
|
178
|
+
export function getTrpcPathsMatchingDescriptionSearch(query) {
|
|
179
|
+
const q = query.trim().toLowerCase();
|
|
180
|
+
if (!q)
|
|
181
|
+
return [];
|
|
182
|
+
const paths = [];
|
|
183
|
+
for (const [path, label] of Object.entries(PATH_LABELS)) {
|
|
184
|
+
if (label.toLowerCase().includes(q)) {
|
|
185
|
+
paths.push(path);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return paths;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Stable, human-readable line for UI and CSV exports.
|
|
192
|
+
*/
|
|
193
|
+
export function getActivityHumanDescription(trpcPath, actionFallback) {
|
|
194
|
+
const path = (trpcPath ?? "").trim();
|
|
195
|
+
if (path) {
|
|
196
|
+
const exact = PATH_LABELS[path];
|
|
197
|
+
if (exact)
|
|
198
|
+
return exact;
|
|
199
|
+
const parts = path.split(".").filter(Boolean);
|
|
200
|
+
if (parts.length >= 2) {
|
|
201
|
+
const proc = parts[parts.length - 1];
|
|
202
|
+
const ns = parts[0];
|
|
203
|
+
const rest = parts.slice(1, -1);
|
|
204
|
+
const action = splitCamelCase(proc);
|
|
205
|
+
const area = titleCaseRouter(ns);
|
|
206
|
+
if (rest.length) {
|
|
207
|
+
return `${area} (${rest.join(" › ")}): ${action}`;
|
|
208
|
+
}
|
|
209
|
+
return `${area}: ${action}`;
|
|
210
|
+
}
|
|
211
|
+
return path;
|
|
212
|
+
}
|
|
213
|
+
const act = (actionFallback ?? "").replace(/^trpc\./, "");
|
|
214
|
+
if (act) {
|
|
215
|
+
const exact = PATH_LABELS[act];
|
|
216
|
+
if (exact)
|
|
217
|
+
return exact;
|
|
218
|
+
return act.replace(/\./g, " › ");
|
|
219
|
+
}
|
|
220
|
+
return "Activity";
|
|
221
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { getActivityHumanDescription, getTrpcPathsMatchingDescriptionSearch, } from "./activity_human_description.js";
|
|
4
|
+
test("description search resolves paths by label substring", () => {
|
|
5
|
+
const paths = getTrpcPathsMatchingDescriptionSearch("study streak");
|
|
6
|
+
assert.ok(paths.includes("workspace.getStudyAnalytics"));
|
|
7
|
+
});
|
|
8
|
+
test("known path returns curated label", () => {
|
|
9
|
+
assert.equal(getActivityHumanDescription("workspace.getStudyAnalytics"), "Loaded study streak and analytics");
|
|
10
|
+
assert.equal(getActivityHumanDescription("payment.getUsageOverview"), "Viewed plan usage and limits");
|
|
11
|
+
});
|
|
12
|
+
test("unknown path gets formatted fallback", () => {
|
|
13
|
+
const d = getActivityHumanDescription("someRouter.unknownProcedureName");
|
|
14
|
+
assert.ok(d.includes("Some Router"));
|
|
15
|
+
assert.ok(d.includes("Unknown"));
|
|
16
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity log persistence (append-only). Env:
|
|
3
|
+
* - ACTIVITY_LOG_ENABLED=true|false (default true)
|
|
4
|
+
* - ACTIVITY_LOG_SAMPLE_RATE=0..1 (default 1)
|
|
5
|
+
* - ACTIVITY_LOG_RETENTION_DAYS (default 365; used by admin.activityPurgeRetention)
|
|
6
|
+
*/
|
|
7
|
+
import type { Prisma, PrismaClient } from "@prisma/client";
|
|
8
|
+
import { ActivityLogCategory, ActivityLogStatus } from "@prisma/client";
|
|
9
|
+
import type { IncomingMessage } from "node:http";
|
|
10
|
+
export declare function isActivityLogEnabled(): boolean;
|
|
11
|
+
export declare function shouldSampleActivity(): boolean;
|
|
12
|
+
/** Recursive redaction for JSON-safe metadata (never log raw credentials). */
|
|
13
|
+
export declare function redactSensitive(value: unknown): unknown;
|
|
14
|
+
export declare function inferCategoryFromTrpcPath(path: string): ActivityLogCategory;
|
|
15
|
+
/** Best-effort workspace id from tRPC input (nested objects included). */
|
|
16
|
+
export declare function extractWorkspaceIdFromInput(raw: unknown): string | undefined;
|
|
17
|
+
export declare function truncateUserAgent(ua: string | undefined, max?: number): string | undefined;
|
|
18
|
+
export declare function getClientIp(req: IncomingMessage): string | undefined;
|
|
19
|
+
export type RecordActivityInput = {
|
|
20
|
+
db: PrismaClient;
|
|
21
|
+
actorUserId: string;
|
|
22
|
+
actorEmailSnapshot?: string | null;
|
|
23
|
+
path: string;
|
|
24
|
+
type: "query" | "mutation" | "subscription";
|
|
25
|
+
status: ActivityLogStatus;
|
|
26
|
+
durationMs: number;
|
|
27
|
+
errorCode?: string | null;
|
|
28
|
+
rawInput?: unknown;
|
|
29
|
+
ipAddress?: string | null;
|
|
30
|
+
userAgent?: string | null;
|
|
31
|
+
httpMethod?: string | null;
|
|
32
|
+
};
|
|
33
|
+
export type RecordExplicitActivityInput = {
|
|
34
|
+
db: PrismaClient;
|
|
35
|
+
actorUserId?: string | null;
|
|
36
|
+
actorEmailSnapshot?: string | null;
|
|
37
|
+
/**
|
|
38
|
+
* A stable action label shown in admin UI/CSV.
|
|
39
|
+
* Example: `cron.purgeDeletedUsers`, `stripe.webhook.checkout.session.completed`
|
|
40
|
+
*/
|
|
41
|
+
action: string;
|
|
42
|
+
category: ActivityLogCategory;
|
|
43
|
+
resourceType?: string | null;
|
|
44
|
+
resourceId?: string | null;
|
|
45
|
+
workspaceId?: string | null;
|
|
46
|
+
trpcPath?: string | null;
|
|
47
|
+
httpMethod?: string | null;
|
|
48
|
+
status: ActivityLogStatus;
|
|
49
|
+
errorCode?: string | null;
|
|
50
|
+
durationMs: number;
|
|
51
|
+
ipAddress?: string | null;
|
|
52
|
+
userAgent?: string | null;
|
|
53
|
+
metadata?: unknown;
|
|
54
|
+
/**
|
|
55
|
+
* When true, bypasses ACTIVITY_LOG_SAMPLE_RATE sampling.
|
|
56
|
+
* Useful for low-volume admin-auditable system events (cron/webhooks).
|
|
57
|
+
*/
|
|
58
|
+
forceWrite?: boolean;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Persists one activity row. Prefer `scheduleRecordActivity` from request path to avoid blocking.
|
|
62
|
+
*/
|
|
63
|
+
export declare function recordActivity(input: RecordActivityInput): Promise<void>;
|
|
64
|
+
export declare function scheduleRecordActivity(input: RecordActivityInput): void;
|
|
65
|
+
/**
|
|
66
|
+
* Persists one explicit (non-tRPC) activity row.
|
|
67
|
+
* This is used for cron jobs, webhooks, and other system-wide background operations.
|
|
68
|
+
*/
|
|
69
|
+
export declare function recordExplicitActivity(input: RecordExplicitActivityInput): Promise<void>;
|
|
70
|
+
export declare function scheduleRecordExplicitActivity(input: RecordExplicitActivityInput): void;
|
|
71
|
+
/** Default retention: 365 days. Override with ACTIVITY_LOG_RETENTION_DAYS. */
|
|
72
|
+
export declare function getActivityRetentionDays(): number;
|
|
73
|
+
export declare function deleteActivityLogsOlderThan(db: PrismaClient, cutoff: Date): Promise<{
|
|
74
|
+
deleted: number;
|
|
75
|
+
}>;
|
|
76
|
+
export type ActivityLogFilter = {
|
|
77
|
+
actorUserId?: string;
|
|
78
|
+
workspaceId?: string;
|
|
79
|
+
from?: Date;
|
|
80
|
+
to?: Date;
|
|
81
|
+
category?: ActivityLogCategory;
|
|
82
|
+
status?: ActivityLogStatus;
|
|
83
|
+
search?: string;
|
|
84
|
+
};
|
|
85
|
+
export declare function buildActivityLogWhere(filter: ActivityLogFilter, options?: {
|
|
86
|
+
forceActorUserId?: string;
|
|
87
|
+
}): Prisma.ActivityLogWhereInput;
|