Checkpoint: v3.0 - 新增训练视频教程库(分类浏览、自评系统)、训练提醒通知(多类型提醒、浏览器推送)、通知记录管理、去除冗余文字。65个测试全部通过。
这个提交包含在:
@@ -471,6 +471,120 @@ ${recentScores.length > 0 ? `- 用户最近的分析数据: ${JSON.stringify(rec
|
||||
return db.getLeaderboard(input?.sortBy || "ntrpRating", input?.limit || 50);
|
||||
}),
|
||||
}),
|
||||
|
||||
// Tutorial video library
|
||||
tutorial: router({
|
||||
list: publicProcedure
|
||||
.input(z.object({
|
||||
category: z.string().optional(),
|
||||
skillLevel: z.string().optional(),
|
||||
}).optional())
|
||||
.query(async ({ input }) => {
|
||||
// Auto-seed tutorials on first request
|
||||
await db.seedTutorials();
|
||||
return db.getTutorials(input?.category, input?.skillLevel);
|
||||
}),
|
||||
|
||||
get: publicProcedure
|
||||
.input(z.object({ id: z.number() }))
|
||||
.query(async ({ input }) => {
|
||||
return db.getTutorialById(input.id);
|
||||
}),
|
||||
|
||||
progress: protectedProcedure.query(async ({ ctx }) => {
|
||||
return db.getUserTutorialProgress(ctx.user.id);
|
||||
}),
|
||||
|
||||
updateProgress: protectedProcedure
|
||||
.input(z.object({
|
||||
tutorialId: z.number(),
|
||||
watched: z.number().optional(),
|
||||
selfScore: z.number().optional(),
|
||||
notes: z.string().optional(),
|
||||
comparisonVideoId: z.number().optional(),
|
||||
}))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { tutorialId, ...data } = input;
|
||||
await db.updateTutorialProgress(ctx.user.id, tutorialId, data);
|
||||
return { success: true };
|
||||
}),
|
||||
}),
|
||||
|
||||
// Training reminders
|
||||
reminder: router({
|
||||
list: protectedProcedure.query(async ({ ctx }) => {
|
||||
return db.getUserReminders(ctx.user.id);
|
||||
}),
|
||||
|
||||
create: protectedProcedure
|
||||
.input(z.object({
|
||||
reminderType: z.string(),
|
||||
title: z.string(),
|
||||
message: z.string().optional(),
|
||||
timeOfDay: z.string(),
|
||||
daysOfWeek: z.array(z.number()),
|
||||
}))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const reminderId = await db.createReminder({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
return { reminderId };
|
||||
}),
|
||||
|
||||
update: protectedProcedure
|
||||
.input(z.object({
|
||||
reminderId: z.number(),
|
||||
title: z.string().optional(),
|
||||
message: z.string().optional(),
|
||||
timeOfDay: z.string().optional(),
|
||||
daysOfWeek: z.array(z.number()).optional(),
|
||||
}))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { reminderId, ...data } = input;
|
||||
await db.updateReminder(reminderId, ctx.user.id, data);
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
delete: protectedProcedure
|
||||
.input(z.object({ reminderId: z.number() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await db.deleteReminder(input.reminderId, ctx.user.id);
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
toggle: protectedProcedure
|
||||
.input(z.object({ reminderId: z.number(), isActive: z.number() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await db.toggleReminder(input.reminderId, ctx.user.id, input.isActive);
|
||||
return { success: true };
|
||||
}),
|
||||
}),
|
||||
|
||||
// Notifications
|
||||
notification: router({
|
||||
list: protectedProcedure
|
||||
.input(z.object({ limit: z.number().default(50) }).optional())
|
||||
.query(async ({ ctx, input }) => {
|
||||
return db.getUserNotifications(ctx.user.id, input?.limit || 50);
|
||||
}),
|
||||
|
||||
unreadCount: protectedProcedure.query(async ({ ctx }) => {
|
||||
return db.getUnreadNotificationCount(ctx.user.id);
|
||||
}),
|
||||
|
||||
markRead: protectedProcedure
|
||||
.input(z.object({ notificationId: z.number() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await db.markNotificationRead(input.notificationId, ctx.user.id);
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
markAllRead: protectedProcedure.mutation(async ({ ctx }) => {
|
||||
await db.markAllNotificationsRead(ctx.user.id);
|
||||
return { success: true };
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
// NTRP Rating calculation function
|
||||
|
||||
在新工单中引用
屏蔽一个用户