Skip to content

Commit fcfa928

Browse files
committed
feat(api): add saved repos tRPC endpoints
- Add getSavedRepos query (protected, feature flag) - Add updateSavedRepos mutation (protected, feature flag) - Implement merge logic for sync - Add Zod validation schemas - Feature flag: FEATURE_SAVED_REPOS_DB - Fix z.record() type arguments for Zod compatibility
1 parent e704336 commit fcfa928

File tree

1 file changed

+77
-0
lines changed

1 file changed

+77
-0
lines changed

apps/api/src/routers/user.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { router, publicProcedure, protectedProcedure } from "../trpc.js";
22
import { userService } from "../services/user.service.js";
3+
import { savedReposService } from "../services/savedRepos.service.js";
34
import { z } from "zod";
45

56
export const userRouter = router({
@@ -34,5 +35,81 @@ export const userRouter = router({
3435
userId,
3536
input.completedSteps
3637
);
38+
}),
39+
40+
// get user's saved repos (feature flag: FEATURE_SAVED_REPOS_DB)
41+
getSavedRepos: protectedProcedure.query(async ({ ctx }: any) => {
42+
if (process.env.FEATURE_SAVED_REPOS_DB !== "true") {
43+
return [];
44+
}
45+
const userId = ctx.user.id;
46+
return await savedReposService.getSavedRepos(ctx.db.prisma, userId);
3747
}),
48+
49+
// update user's saved repos with merge logic (feature flag: FEATURE_SAVED_REPOS_DB)
50+
updateSavedRepos: protectedProcedure
51+
.input(
52+
z.object({
53+
action: z.enum(["add", "remove", "replace"]),
54+
repos: z.array(
55+
z.object({
56+
id: z.string(),
57+
name: z.string(),
58+
url: z.string(),
59+
language: z.string().optional(),
60+
popularity: z.enum(["low", "medium", "high"]).optional(),
61+
competitionScore: z.number().optional(),
62+
savedAt: z.string(),
63+
meta: z.record(z.string(), z.any()).optional(),
64+
})
65+
),
66+
localRepos: z
67+
.array(
68+
z.object({
69+
id: z.string(),
70+
name: z.string(),
71+
url: z.string(),
72+
language: z.string().optional(),
73+
popularity: z.enum(["low", "medium", "high"]).optional(),
74+
competitionScore: z.number().optional(),
75+
savedAt: z.string(),
76+
meta: z.record(z.string(), z.any()).optional(),
77+
})
78+
)
79+
.optional(),
80+
})
81+
)
82+
.mutation(async ({ ctx, input }: any) => {
83+
if (process.env.FEATURE_SAVED_REPOS_DB !== "true") {
84+
throw new Error("Saved repos sync is not enabled");
85+
}
86+
87+
const userId = ctx.user.id;
88+
89+
// If localRepos provided, merge with server repos
90+
if (input.localRepos && input.action === "replace") {
91+
const serverRepos = await savedReposService.getSavedRepos(
92+
ctx.db.prisma,
93+
userId
94+
);
95+
const merged = savedReposService.mergeSavedRepos(
96+
input.localRepos,
97+
serverRepos
98+
);
99+
return await savedReposService.updateSavedRepos(
100+
ctx.db.prisma,
101+
userId,
102+
"replace",
103+
merged
104+
);
105+
}
106+
107+
// Otherwise, perform the requested action
108+
return await savedReposService.updateSavedRepos(
109+
ctx.db.prisma,
110+
userId,
111+
input.action,
112+
input.repos
113+
);
114+
}),
38115
});

0 commit comments

Comments
 (0)