aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/server/routes/sync.ts
blob: ff95bf4c128fe14301f70ec987e93889d788dec5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import { zValidator } from "@hono/zod-validator";
import { Hono } from "hono";
import { z } from "zod";
import { authMiddleware, getAuthUser } from "../middleware/index.js";
import {
	type SyncPullQuery,
	type SyncPushData,
	type SyncRepository,
	syncRepository,
} from "../repositories/sync.js";

export interface SyncDependencies {
	syncRepo: SyncRepository;
}

const syncDeckSchema = z.object({
	id: z.uuid(),
	name: z.string().min(1).max(255),
	description: z.string().nullable(),
	newCardsPerDay: z.number().int().min(0).max(1000),
	createdAt: z.string().datetime(),
	updatedAt: z.string().datetime(),
	deletedAt: z.string().datetime().nullable(),
});

const syncCardSchema = z.object({
	id: z.uuid(),
	deckId: z.uuid(),
	front: z.string().min(1),
	back: z.string().min(1),
	state: z.number().int().min(0).max(3),
	due: z.string().datetime(),
	stability: z.number().min(0),
	difficulty: z.number().min(0),
	elapsedDays: z.number().int().min(0),
	scheduledDays: z.number().int().min(0),
	reps: z.number().int().min(0),
	lapses: z.number().int().min(0),
	lastReview: z.string().datetime().nullable(),
	createdAt: z.string().datetime(),
	updatedAt: z.string().datetime(),
	deletedAt: z.string().datetime().nullable(),
});

const syncReviewLogSchema = z.object({
	id: z.uuid(),
	cardId: z.uuid(),
	rating: z.number().int().min(1).max(4),
	state: z.number().int().min(0).max(3),
	scheduledDays: z.number().int().min(0),
	elapsedDays: z.number().int().min(0),
	reviewedAt: z.string().datetime(),
	durationMs: z.number().int().min(0).nullable(),
});

const syncPushSchema = z.object({
	decks: z.array(syncDeckSchema).default([]),
	cards: z.array(syncCardSchema).default([]),
	reviewLogs: z.array(syncReviewLogSchema).default([]),
});

const syncPullQuerySchema = z.object({
	lastSyncVersion: z.coerce.number().int().min(0).default(0),
});

export function createSyncRouter(deps: SyncDependencies) {
	const { syncRepo } = deps;

	return new Hono()
		.use("*", authMiddleware)
		.post("/push", zValidator("json", syncPushSchema), async (c) => {
			const user = getAuthUser(c);
			const data = c.req.valid("json") as SyncPushData;

			const result = await syncRepo.pushChanges(user.id, data);

			return c.json(result, 200);
		})
		.get("/pull", zValidator("query", syncPullQuerySchema), async (c) => {
			const user = getAuthUser(c);
			const query = c.req.valid("query") as SyncPullQuery;

			const result = await syncRepo.pullChanges(user.id, query);

			return c.json(result, 200);
		});
}

export const sync = createSyncRouter({
	syncRepo: syncRepository,
});