This commit is contained in:
Michael Freno
2026-01-21 03:03:08 -05:00
parent 5fc082178c
commit 0abe064afd

View File

@@ -8,7 +8,8 @@ const CAIRN_CACHE_TTL_MS = 5 * 60 * 1000;
const paginatedQuerySchema = z.object({ const paginatedQuerySchema = z.object({
limit: z.number().int().min(1).max(100).optional(), limit: z.number().int().min(1).max(100).optional(),
offset: z.number().int().min(0).optional() offset: z.number().int().min(0).optional(),
since: z.string().optional()
}); });
const planIdSchema = z.object({ id: z.string().min(1) }); const planIdSchema = z.object({ id: z.string().min(1) });
@@ -187,10 +188,10 @@ export const remoteDbRouter = createTRPCRouter({
getUsers: cairnProcedure getUsers: cairnProcedure
.input(paginatedQuerySchema) .input(paginatedQuerySchema)
.query(async ({ input }) => { .query(async ({ input, ctx }) => {
const limit = input.limit ?? 50; const limit = input.limit ?? 50;
const offset = input.offset ?? 0; const offset = input.offset ?? 0;
const cacheKey = `cairn-users:${limit}:${offset}`; const cacheKey = `cairn-users:${ctx.cairnUserId}:${limit}:${offset}:${input.since ?? ""}`;
const cached = await cache.get<{ const cached = await cache.get<{
users: Array<{ id: string; email: string | null }>; users: Array<{ id: string; email: string | null }>;
@@ -201,9 +202,15 @@ export const remoteDbRouter = createTRPCRouter({
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
const sql = input.since
? "SELECT id, email FROM users WHERE id = ? AND updatedAt > ? ORDER BY createdAt DESC LIMIT ? OFFSET ?"
: "SELECT id, email FROM users WHERE id = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?";
const args = input.since
? [ctx.cairnUserId, input.since, limit, offset]
: [ctx.cairnUserId, limit, offset];
const result = await conn.execute({ const result = await conn.execute({
sql: "SELECT id, email FROM users ORDER BY createdAt DESC LIMIT ? OFFSET ?", sql,
args: [limit, offset] args
}); });
const payload = { const payload = {
@@ -223,10 +230,10 @@ export const remoteDbRouter = createTRPCRouter({
getPlans: cairnProcedure getPlans: cairnProcedure
.input(paginatedQuerySchema) .input(paginatedQuerySchema)
.query(async ({ input }) => { .query(async ({ input, ctx }) => {
const limit = input.limit ?? 50; const limit = input.limit ?? 50;
const offset = input.offset ?? 0; const offset = input.offset ?? 0;
const cacheKey = `cairn-plans:${limit}:${offset}`; const cacheKey = `cairn-plans:${ctx.cairnUserId}:${limit}:${offset}:${input.since ?? ""}`;
const cached = await cache.get<{ const cached = await cache.get<{
plans: Array<{ id: string; name: string; category: string }>; plans: Array<{ id: string; name: string; category: string }>;
@@ -237,9 +244,15 @@ export const remoteDbRouter = createTRPCRouter({
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
const sql = input.since
? "SELECT id, name, category FROM workoutPlans WHERE userId = ? AND updatedAt > ? ORDER BY createdAt DESC LIMIT ? OFFSET ?"
: "SELECT id, name, category FROM workoutPlans WHERE userId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?";
const args = input.since
? [ctx.cairnUserId, input.since, limit, offset]
: [ctx.cairnUserId, limit, offset];
const result = await conn.execute({ const result = await conn.execute({
sql: "SELECT id, name, category FROM workoutPlans ORDER BY createdAt DESC LIMIT ? OFFSET ?", sql,
args: [limit, offset] args
}); });
const payload = { const payload = {
@@ -263,10 +276,10 @@ export const remoteDbRouter = createTRPCRouter({
getWorkouts: cairnProcedure getWorkouts: cairnProcedure
.input(paginatedQuerySchema) .input(paginatedQuerySchema)
.query(async ({ input }) => { .query(async ({ input, ctx }) => {
const limit = input.limit ?? 50; const limit = input.limit ?? 50;
const offset = input.offset ?? 0; const offset = input.offset ?? 0;
const cacheKey = `cairn-workouts:${limit}:${offset}`; const cacheKey = `cairn-workouts:${ctx.cairnUserId}:${limit}:${offset}:${input.since ?? ""}`;
const cached = await cache.get<{ const cached = await cache.get<{
workouts: Array<{ id: string; type: string; startDate: string }>; workouts: Array<{ id: string; type: string; startDate: string }>;
@@ -277,9 +290,15 @@ export const remoteDbRouter = createTRPCRouter({
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
const sql = input.since
? "SELECT id, type, startDate FROM workouts WHERE userId = ? AND updatedAt > ? ORDER BY startDate DESC LIMIT ? OFFSET ?"
: "SELECT id, type, startDate FROM workouts WHERE userId = ? ORDER BY startDate DESC LIMIT ? OFFSET ?";
const args = input.since
? [ctx.cairnUserId, input.since, limit, offset]
: [ctx.cairnUserId, limit, offset];
const result = await conn.execute({ const result = await conn.execute({
sql: "SELECT id, type, startDate FROM workouts ORDER BY startDate DESC LIMIT ? OFFSET ?", sql,
args: [limit, offset] args
}); });
const payload = { const payload = {
@@ -303,12 +322,16 @@ export const remoteDbRouter = createTRPCRouter({
createUser: cairnProcedure createUser: cairnProcedure
.input(userInputSchema) .input(userInputSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
if (input.id !== ctx.cairnUserId) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
await conn.execute({ await conn.execute({
sql: `INSERT INTO users (id, email, emailVerified, firstName, lastName, displayName, avatarUrl, provider, appleUserId, status) sql: `INSERT INTO users (id, email, emailVerified, firstName, lastName, displayName, avatarUrl, provider, appleUserId, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET email = excluded.email, emailVerified = excluded.emailVerified, firstName = excluded.firstName, lastName = excluded.lastName, displayName = excluded.displayName, avatarUrl = excluded.avatarUrl, provider = excluded.provider, appleUserId = excluded.appleUserId, status = excluded.status, updatedAt = datetime('now')`,
args: [ args: [
input.id, input.id,
input.email ?? null, input.email ?? null,
@@ -334,7 +357,10 @@ export const remoteDbRouter = createTRPCRouter({
updateUser: cairnProcedure updateUser: cairnProcedure
.input(userInputSchema) .input(userInputSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
if (input.id !== ctx.cairnUserId) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
await conn.execute({ await conn.execute({
@@ -364,7 +390,10 @@ export const remoteDbRouter = createTRPCRouter({
deleteUser: cairnProcedure deleteUser: cairnProcedure
.input(userIdSchema) .input(userIdSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
if (input.id !== ctx.cairnUserId) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
await conn.execute({ await conn.execute({
@@ -383,7 +412,10 @@ export const remoteDbRouter = createTRPCRouter({
createWorkoutPlan: cairnProcedure createWorkoutPlan: cairnProcedure
.input(workoutPlanSchema) .input(workoutPlanSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
if (input.userId != ctx.cairnUserId) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
await conn.execute({ await conn.execute({
@@ -413,7 +445,10 @@ export const remoteDbRouter = createTRPCRouter({
updateWorkoutPlan: cairnProcedure updateWorkoutPlan: cairnProcedure
.input(workoutPlanSchema) .input(workoutPlanSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
if (input.userId != ctx.cairnUserId) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
await conn.execute({ await conn.execute({
@@ -441,9 +476,16 @@ export const remoteDbRouter = createTRPCRouter({
deleteWorkoutPlan: cairnProcedure deleteWorkoutPlan: cairnProcedure
.input(planIdSchema) .input(planIdSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
const check = await conn.execute({
sql: "SELECT userId FROM workoutPlans WHERE id = ?",
args: [input.id]
});
if (!check.rows.length || check.rows[0].userId !== ctx.cairnUserId) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
await conn.execute({ await conn.execute({
sql: "DELETE FROM workoutPlans WHERE id = ?", sql: "DELETE FROM workoutPlans WHERE id = ?",
args: [input.id] args: [input.id]
@@ -460,9 +502,19 @@ export const remoteDbRouter = createTRPCRouter({
createPlanExercise: cairnProcedure createPlanExercise: cairnProcedure
.input(planExerciseSchema) .input(planExerciseSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
const planCheck = await conn.execute({
sql: "SELECT userId FROM workoutPlans WHERE id = ?",
args: [input.planId]
});
if (
!planCheck.rows.length ||
planCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
await conn.execute({ await conn.execute({
sql: `INSERT INTO planExercises (id, planId, exerciseId, name, category, orderIndex, notes) sql: `INSERT INTO planExercises (id, planId, exerciseId, name, category, orderIndex, notes)
VALUES (?, ?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?)`,
@@ -488,9 +540,19 @@ export const remoteDbRouter = createTRPCRouter({
updatePlanExercise: cairnProcedure updatePlanExercise: cairnProcedure
.input(planExerciseSchema) .input(planExerciseSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
const planCheck = await conn.execute({
sql: "SELECT userId FROM workoutPlans WHERE id = ?",
args: [input.planId]
});
if (
!planCheck.rows.length ||
planCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
await conn.execute({ await conn.execute({
sql: `UPDATE planExercises SET exerciseId = ?, name = ?, category = ?, orderIndex = ?, notes = ? WHERE id = ?`, sql: `UPDATE planExercises SET exerciseId = ?, name = ?, category = ?, orderIndex = ?, notes = ? WHERE id = ?`,
args: [ args: [
@@ -514,9 +576,19 @@ export const remoteDbRouter = createTRPCRouter({
deletePlanExercise: cairnProcedure deletePlanExercise: cairnProcedure
.input(planExerciseIdSchema) .input(planExerciseIdSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
const planCheck = await conn.execute({
sql: "SELECT workoutPlans.userId FROM workoutPlans INNER JOIN planExercises ON planExercises.planId = workoutPlans.id WHERE planExercises.id = ?",
args: [input.id]
});
if (
!planCheck.rows.length ||
planCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
await conn.execute({ await conn.execute({
sql: "DELETE FROM planExercises WHERE id = ?", sql: "DELETE FROM planExercises WHERE id = ?",
args: [input.id] args: [input.id]
@@ -533,9 +605,19 @@ export const remoteDbRouter = createTRPCRouter({
createPlanSet: cairnProcedure createPlanSet: cairnProcedure
.input(planSetSchema) .input(planSetSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
const planCheck = await conn.execute({
sql: "SELECT workoutPlans.userId FROM workoutPlans INNER JOIN planExercises ON planExercises.planId = workoutPlans.id WHERE planExercises.id = ?",
args: [input.planExerciseId]
});
if (
!planCheck.rows.length ||
planCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
await conn.execute({ await conn.execute({
sql: `INSERT INTO planSets (id, planExerciseId, setNumber, reps, weight, durationSeconds, rpe, restAfterSeconds, isWarmup, isDropset, notes) sql: `INSERT INTO planSets (id, planExerciseId, setNumber, reps, weight, durationSeconds, rpe, restAfterSeconds, isWarmup, isDropset, notes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -565,9 +647,19 @@ export const remoteDbRouter = createTRPCRouter({
updatePlanSet: cairnProcedure updatePlanSet: cairnProcedure
.input(planSetSchema) .input(planSetSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
const planCheck = await conn.execute({
sql: "SELECT workoutPlans.userId FROM workoutPlans INNER JOIN planExercises ON planExercises.planId = workoutPlans.id INNER JOIN planSets ON planSets.planExerciseId = planExercises.id WHERE planSets.id = ?",
args: [input.id]
});
if (
!planCheck.rows.length ||
planCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
await conn.execute({ await conn.execute({
sql: `UPDATE planSets SET setNumber = ?, reps = ?, weight = ?, durationSeconds = ?, rpe = ?, restAfterSeconds = ?, isWarmup = ?, isDropset = ?, notes = ? WHERE id = ?`, sql: `UPDATE planSets SET setNumber = ?, reps = ?, weight = ?, durationSeconds = ?, rpe = ?, restAfterSeconds = ?, isWarmup = ?, isDropset = ?, notes = ? WHERE id = ?`,
args: [ args: [
@@ -595,9 +687,19 @@ export const remoteDbRouter = createTRPCRouter({
deletePlanSet: cairnProcedure deletePlanSet: cairnProcedure
.input(planSetIdSchema) .input(planSetIdSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
const planCheck = await conn.execute({
sql: "SELECT workoutPlans.userId FROM workoutPlans INNER JOIN planExercises ON planExercises.planId = workoutPlans.id INNER JOIN planSets ON planSets.planExerciseId = planExercises.id WHERE planSets.id = ?",
args: [input.id]
});
if (
!planCheck.rows.length ||
planCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
await conn.execute({ await conn.execute({
sql: "DELETE FROM planSets WHERE id = ?", sql: "DELETE FROM planSets WHERE id = ?",
args: [input.id] args: [input.id]
@@ -614,9 +716,19 @@ export const remoteDbRouter = createTRPCRouter({
createRoutePoint: cairnProcedure createRoutePoint: cairnProcedure
.input(routePointSchema) .input(routePointSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
const planCheck = await conn.execute({
sql: "SELECT userId FROM workoutPlans WHERE id = ?",
args: [input.planId]
});
if (
!planCheck.rows.length ||
planCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
await conn.execute({ await conn.execute({
sql: `INSERT INTO routePoints (id, planId, latitude, longitude, orderIndex, isWaypoint) sql: `INSERT INTO routePoints (id, planId, latitude, longitude, orderIndex, isWaypoint)
VALUES (?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?)`,
@@ -641,9 +753,19 @@ export const remoteDbRouter = createTRPCRouter({
updateRoutePoint: cairnProcedure updateRoutePoint: cairnProcedure
.input(routePointSchema) .input(routePointSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
const planCheck = await conn.execute({
sql: "SELECT workoutPlans.userId FROM workoutPlans INNER JOIN routePoints ON routePoints.planId = workoutPlans.id WHERE routePoints.id = ?",
args: [input.id]
});
if (
!planCheck.rows.length ||
planCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
await conn.execute({ await conn.execute({
sql: `UPDATE routePoints SET latitude = ?, longitude = ?, orderIndex = ?, isWaypoint = ? WHERE id = ?`, sql: `UPDATE routePoints SET latitude = ?, longitude = ?, orderIndex = ?, isWaypoint = ? WHERE id = ?`,
args: [ args: [
@@ -666,9 +788,19 @@ export const remoteDbRouter = createTRPCRouter({
deleteRoutePoint: cairnProcedure deleteRoutePoint: cairnProcedure
.input(routePointIdSchema) .input(routePointIdSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
const planCheck = await conn.execute({
sql: "SELECT workoutPlans.userId FROM workoutPlans INNER JOIN routePoints ON routePoints.planId = workoutPlans.id WHERE routePoints.id = ?",
args: [input.id]
});
if (
!planCheck.rows.length ||
planCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
await conn.execute({ await conn.execute({
sql: "DELETE FROM routePoints WHERE id = ?", sql: "DELETE FROM routePoints WHERE id = ?",
args: [input.id] args: [input.id]
@@ -685,7 +817,10 @@ export const remoteDbRouter = createTRPCRouter({
createWorkout: cairnProcedure createWorkout: cairnProcedure
.input(workoutSchema) .input(workoutSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
if (input.userId != ctx.cairnUserId) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
await conn.execute({ await conn.execute({
@@ -724,7 +859,10 @@ export const remoteDbRouter = createTRPCRouter({
updateWorkout: cairnProcedure updateWorkout: cairnProcedure
.input(workoutSchema) .input(workoutSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
if (input.userId != ctx.cairnUserId) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
await conn.execute({ await conn.execute({
@@ -761,9 +899,16 @@ export const remoteDbRouter = createTRPCRouter({
deleteWorkout: cairnProcedure deleteWorkout: cairnProcedure
.input(workoutIdSchema) .input(workoutIdSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
const check = await conn.execute({
sql: "SELECT userId FROM workouts WHERE id = ?",
args: [input.id]
});
if (!check.rows.length || check.rows[0].userId !== ctx.cairnUserId) {
throw new TRPCError({ code: "FORBIDDEN", message: "User mismatch" });
}
await conn.execute({ await conn.execute({
sql: "DELETE FROM workouts WHERE id = ?", sql: "DELETE FROM workouts WHERE id = ?",
args: [input.id] args: [input.id]
@@ -811,12 +956,7 @@ export const remoteDbRouter = createTRPCRouter({
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
await conn.execute({ await conn.execute({
sql: `UPDATE heartRateSamples SET timestamp = ?, bpm = ?, source = ? WHERE id = ?`, sql: `UPDATE heartRateSamples SET timestamp = ?, bpm = ?, source = ? WHERE id = ?`,
args: [ args: [input.timestamp, input.bpm, input.source ?? null, input.id]
input.timestamp,
input.bpm,
input.source ?? null,
input.id
]
}); });
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
@@ -1162,12 +1302,52 @@ export const remoteDbRouter = createTRPCRouter({
bulkUpsert: cairnProcedure bulkUpsert: cairnProcedure
.input(bulkSchema) .input(bulkSchema)
.mutation(async ({ input }) => { .mutation(async ({ input, ctx }) => {
try { try {
const conn = CairnConnectionFactory(); const conn = CairnConnectionFactory();
if (input.users?.length) { if (input.users?.length) {
for (const user of input.users) { for (const user of input.users) {
if (user.id !== ctx.cairnUserId) {
throw new TRPCError({
code: "FORBIDDEN",
message: "User mismatch"
});
}
await conn.execute({
sql: `INSERT INTO users (id, email, emailVerified, firstName, lastName, displayName, avatarUrl, provider, appleUserId, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET email = excluded.email, emailVerified = excluded.emailVerified, firstName = excluded.firstName, lastName = excluded.lastName, displayName = excluded.displayName, avatarUrl = excluded.avatarUrl, provider = excluded.provider, appleUserId = excluded.appleUserId, status = excluded.status, updatedAt = datetime('now')`,
args: [
user.id,
user.email ?? null,
user.emailVerified ?? 0,
user.firstName ?? null,
user.lastName ?? null,
user.displayName ?? null,
user.avatarUrl ?? null,
user.provider ?? null,
user.appleUserId ?? null,
user.status ?? "active"
]
});
}
}
bulkUpsert: cairnProcedure
.input(bulkSchema)
.mutation(async ({ input, ctx }) => {
try {
const conn = CairnConnectionFactory();
if (input.users?.length) {
for (const user of input.users) {
if (user.id !== ctx.cairnUserId) {
throw new TRPCError({
code: "FORBIDDEN",
message: "User mismatch"
});
}
await conn.execute({ await conn.execute({
sql: `INSERT INTO users (id, email, emailVerified, firstName, lastName, displayName, avatarUrl, provider, appleUserId, status) sql: `INSERT INTO users (id, email, emailVerified, firstName, lastName, displayName, avatarUrl, provider, appleUserId, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -1212,6 +1392,12 @@ export const remoteDbRouter = createTRPCRouter({
if (input.workoutPlans?.length) { if (input.workoutPlans?.length) {
for (const plan of input.workoutPlans) { for (const plan of input.workoutPlans) {
if (plan.userId !== ctx.cairnUserId) {
throw new TRPCError({
code: "FORBIDDEN",
message: "User mismatch"
});
}
await conn.execute({ await conn.execute({
sql: `INSERT INTO workoutPlans (id, userId, name, description, category, difficulty, durationMinutes, type, isPublic) sql: `INSERT INTO workoutPlans (id, userId, name, description, category, difficulty, durationMinutes, type, isPublic)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -1233,6 +1419,19 @@ export const remoteDbRouter = createTRPCRouter({
if (input.planExercises?.length) { if (input.planExercises?.length) {
for (const planExercise of input.planExercises) { for (const planExercise of input.planExercises) {
const planCheck = await conn.execute({
sql: "SELECT userId FROM workoutPlans WHERE id = ?",
args: [planExercise.planId]
});
if (
!planCheck.rows.length ||
planCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({
code: "FORBIDDEN",
message: "User mismatch"
});
}
await conn.execute({ await conn.execute({
sql: `INSERT INTO planExercises (id, planId, exerciseId, name, category, orderIndex, notes) sql: `INSERT INTO planExercises (id, planId, exerciseId, name, category, orderIndex, notes)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
@@ -1252,6 +1451,25 @@ export const remoteDbRouter = createTRPCRouter({
if (input.planSets?.length) { if (input.planSets?.length) {
for (const planSet of input.planSets) { for (const planSet of input.planSets) {
const planExerciseCheck = await conn.execute({
sql: "SELECT planId FROM planExercises WHERE id = ?",
args: [planSet.planExerciseId]
});
if (planExerciseCheck.rows.length) {
const planCheck = await conn.execute({
sql: "SELECT userId FROM workoutPlans WHERE id = ?",
args: [planExerciseCheck.rows[0].planId]
});
if (
!planCheck.rows.length ||
planCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({
code: "FORBIDDEN",
message: "User mismatch"
});
}
}
await conn.execute({ await conn.execute({
sql: `INSERT INTO planSets (id, planExerciseId, setNumber, reps, weight, durationSeconds, rpe, restAfterSeconds, isWarmup, isDropset, notes) sql: `INSERT INTO planSets (id, planExerciseId, setNumber, reps, weight, durationSeconds, rpe, restAfterSeconds, isWarmup, isDropset, notes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -1275,6 +1493,19 @@ export const remoteDbRouter = createTRPCRouter({
if (input.routePoints?.length) { if (input.routePoints?.length) {
for (const point of input.routePoints) { for (const point of input.routePoints) {
const planCheck = await conn.execute({
sql: "SELECT userId FROM workoutPlans WHERE id = ?",
args: [point.planId]
});
if (
!planCheck.rows.length ||
planCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({
code: "FORBIDDEN",
message: "User mismatch"
});
}
await conn.execute({ await conn.execute({
sql: `INSERT INTO routePoints (id, planId, latitude, longitude, orderIndex, isWaypoint) sql: `INSERT INTO routePoints (id, planId, latitude, longitude, orderIndex, isWaypoint)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
@@ -1293,6 +1524,12 @@ export const remoteDbRouter = createTRPCRouter({
if (input.workouts?.length) { if (input.workouts?.length) {
for (const workout of input.workouts) { for (const workout of input.workouts) {
if (workout.userId !== ctx.cairnUserId) {
throw new TRPCError({
code: "FORBIDDEN",
message: "User mismatch"
});
}
await conn.execute({ await conn.execute({
sql: `INSERT INTO workouts (id, userId, planId, type, name, startDate, endDate, durationSeconds, distanceMeters, calories, averageHeartRate, maxHeartRate, averagePace, elevationGain, status, source, healthKitUUID, notes) sql: `INSERT INTO workouts (id, userId, planId, type, name, startDate, endDate, durationSeconds, distanceMeters, calories, averageHeartRate, maxHeartRate, averagePace, elevationGain, status, source, healthKitUUID, notes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -1323,6 +1560,19 @@ export const remoteDbRouter = createTRPCRouter({
if (input.heartRateSamples?.length) { if (input.heartRateSamples?.length) {
for (const sample of input.heartRateSamples) { for (const sample of input.heartRateSamples) {
const workoutCheck = await conn.execute({
sql: "SELECT userId FROM workouts WHERE id = ?",
args: [sample.workoutId]
});
if (
!workoutCheck.rows.length ||
workoutCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({
code: "FORBIDDEN",
message: "User mismatch"
});
}
await conn.execute({ await conn.execute({
sql: `INSERT INTO heartRateSamples (id, workoutId, timestamp, bpm, source) sql: `INSERT INTO heartRateSamples (id, workoutId, timestamp, bpm, source)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
@@ -1340,6 +1590,19 @@ export const remoteDbRouter = createTRPCRouter({
if (input.locationSamples?.length) { if (input.locationSamples?.length) {
for (const sample of input.locationSamples) { for (const sample of input.locationSamples) {
const workoutCheck = await conn.execute({
sql: "SELECT userId FROM workouts WHERE id = ?",
args: [sample.workoutId]
});
if (
!workoutCheck.rows.length ||
workoutCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({
code: "FORBIDDEN",
message: "User mismatch"
});
}
await conn.execute({ await conn.execute({
sql: `INSERT INTO locationSamples (id, workoutId, timestamp, latitude, longitude, altitude, horizontalAccuracy, verticalAccuracy, speed, course) sql: `INSERT INTO locationSamples (id, workoutId, timestamp, latitude, longitude, altitude, horizontalAccuracy, verticalAccuracy, speed, course)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -1362,6 +1625,19 @@ export const remoteDbRouter = createTRPCRouter({
if (input.workoutSplits?.length) { if (input.workoutSplits?.length) {
for (const split of input.workoutSplits) { for (const split of input.workoutSplits) {
const workoutCheck = await conn.execute({
sql: "SELECT userId FROM workouts WHERE id = ?",
args: [split.workoutId]
});
if (
!workoutCheck.rows.length ||
workoutCheck.rows[0].userId !== ctx.cairnUserId
) {
throw new TRPCError({
code: "FORBIDDEN",
message: "User mismatch"
});
}
await conn.execute({ await conn.execute({
sql: `INSERT INTO workoutSplits (id, workoutId, splitNumber, distanceMeters, durationSeconds, startTimestamp, endTimestamp, averageHeartRate, averagePace, elevationGain, elevationLoss) sql: `INSERT INTO workoutSplits (id, workoutId, splitNumber, distanceMeters, durationSeconds, startTimestamp, endTimestamp, averageHeartRate, averagePace, elevationGain, elevationLoss)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -1385,6 +1661,12 @@ export const remoteDbRouter = createTRPCRouter({
if (input.authProviders?.length) { if (input.authProviders?.length) {
for (const provider of input.authProviders) { for (const provider of input.authProviders) {
if (provider.userId !== ctx.cairnUserId) {
throw new TRPCError({
code: "FORBIDDEN",
message: "User mismatch"
});
}
await conn.execute({ await conn.execute({
sql: `INSERT INTO authProviders (id, userId, provider, providerUserId, email, displayName, avatarUrl) sql: `INSERT INTO authProviders (id, userId, provider, providerUserId, email, displayName, avatarUrl)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
@@ -1402,6 +1684,16 @@ export const remoteDbRouter = createTRPCRouter({
} }
} }
return { success: true };
} catch (error) {
console.error("Failed bulk upsert:", error);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Failed to bulk upsert"
});
}
});
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
console.error("Failed bulk upsert:", error); console.error("Failed bulk upsert:", error);