Checkpoint: v3.0 - 新增训练视频教程库(分类浏览、自评系统)、训练提醒通知(多类型提醒、浏览器推送)、通知记录管理、去除冗余文字。65个测试全部通过。

这个提交包含在:
Manus
2026-03-14 08:28:57 -04:00
父节点 2c418b482e
当前提交 27083d5af9
修改 19 个文件,包含 2856 行新增32 行删除

查看文件

@@ -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