Files
SwimBuddy/src/db/operations.ts
T

319 lines
9.5 KiB
TypeScript

import { db } from "./client";
import { generateUUID } from "../utils/uuid";
import { workouts, workoutSessions, sets, laps, templates } from "./schema";
import { eq, desc, sql } from "drizzle-orm";
import type { WorkoutSet, LapData } from "@types/workout";
import WatchConnectivity from "../modules/watch-connectivity";
// ─── Templates ───────────────────────────────────────────────
export async function saveTemplate(name: string, poolLength: number, setsData: WorkoutSet[]) {
const id = generateUUID();
await db.insert(templates).values({
id,
name,
poolLength,
jsonData: JSON.stringify(setsData),
createdAt: new Date().toISOString(),
});
return id;
}
export async function getTemplates() {
return db.select().from(templates).orderBy(desc(templates.createdAt));
}
export async function deleteTemplate(id: string) {
await db.delete(templates).where(eq(templates.id, id));
}
export async function toggleTemplateFavorite(id: string, isFavorite: boolean) {
await db.update(templates).set({ isFavorite }).where(eq(templates.id, id));
}
// ─── Workout Plans ───────────────────────────────────────────
export async function saveWorkout(
name: string | null,
poolLength: number,
setsData: WorkoutSet[],
templateId?: string
) {
const workoutId = generateUUID();
await db.insert(workouts).values({
id: workoutId,
name,
templateId: templateId ?? null,
poolLength,
createdAt: new Date().toISOString(),
});
for (let i = 0; i < setsData.length; i++) {
const s = setsData[i];
await db.insert(sets).values({
id: generateUUID(),
workoutId,
position: i + 1,
stroke: s.stroke,
distancePerRep: s.distancePerRep,
repCount: s.repCount,
restType: s.restType,
restDuration: s.restDuration,
targetTime: s.targetTime ?? null,
});
}
return workoutId;
}
export async function getWorkoutWithSets(workoutId: string) {
const workout = await db.select().from(workouts).where(eq(workouts.id, workoutId)).get();
if (!workout) return null;
const workoutSets = await db.select().from(sets).where(eq(sets.workoutId, workoutId));
return { ...workout, sets: workoutSets };
}
export async function getAllWorkouts() {
return db.select().from(workouts).orderBy(desc(workouts.createdAt));
}
// ─── Workout Sessions ────────────────────────────────────────
export async function finishWorkout(
workoutId: string,
duration: number,
calories: number,
avgHeartRate: number,
totalDistance: number
) {
const sessionId = generateUUID();
const workout = await db.select().from(workouts).where(eq(workouts.id, workoutId)).get();
await db.insert(workoutSessions).values({
id: sessionId,
workoutId,
name: workout?.name ?? null,
poolLength: workout?.poolLength ?? 25,
startedAt: new Date().toISOString(),
finishedAt: new Date().toISOString(),
totalDistance,
totalDuration: duration,
calories,
avgHeartRate,
source: "phone",
});
return sessionId;
}
export async function saveWatchCompletedWorkout(data: {
workoutId: string;
sessionId?: string;
duration: number;
totalDistance: number;
calories: number;
laps: Array<{
setPosition: number;
repNumber: number;
distance: number;
duration: number;
heartRate: number;
strokeCount?: number;
}>;
}) {
const avgHeartRate =
data.laps.length > 0
? Math.round(data.laps.reduce((sum, l) => sum + (l.heartRate || 0), 0) / data.laps.length)
: 0;
// Haal plan op als het bestaat
const plan = await db
.select()
.from(workouts)
.where(eq(workouts.id, data.workoutId))
.get();
// Gebruik sessionId van de watch, of genereer een nieuwe
const sessionId = data.sessionId ?? generateUUID();
// Check of er al een sessie bestaat voor dit sessionId
const existingSession = await db
.select({ id: workoutSessions.id })
.from(workoutSessions)
.where(eq(workoutSessions.id, sessionId))
.limit(1);
if (existingSession.length === 0) {
await db.insert(workoutSessions).values({
id: sessionId,
workoutId: data.workoutId,
name: plan?.name ?? "Watch Workout",
poolLength: plan?.poolLength ?? 25,
startedAt: new Date().toISOString(),
finishedAt: new Date().toISOString(),
totalDistance: data.totalDistance || 0,
totalDuration: data.duration,
calories: data.calories || 0,
avgHeartRate,
source: "watch",
});
} else {
await db
.update(workoutSessions)
.set({
finishedAt: new Date().toISOString(),
totalDuration: data.duration,
totalDistance: data.totalDistance || 0,
calories: data.calories || 0,
avgHeartRate,
})
.where(eq(workoutSessions.id, sessionId));
}
for (const lap of data.laps) {
const strokeCount = lap.strokeCount ?? null;
const swolf = strokeCount != null ? strokeCount + Math.round(lap.duration) : null;
await db.insert(laps).values({
id: generateUUID(),
sessionId,
setPosition: lap.setPosition,
repNumber: lap.repNumber,
distance: lap.distance,
duration: lap.duration,
heartRate: lap.heartRate || null,
strokeCount,
swolf,
});
}
}
export async function getAllSessions() {
return db
.select()
.from(workoutSessions)
.orderBy(desc(workoutSessions.startedAt));
}
export async function getSessionWithDetails(sessionId: string) {
const session = await db
.select()
.from(workoutSessions)
.where(eq(workoutSessions.id, sessionId))
.get();
if (!session) return null;
const sessionLaps = await db.select().from(laps).where(eq(laps.sessionId, sessionId));
let sessionSets: any[] = [];
if (session.workoutId) {
sessionSets = await db.select().from(sets).where(eq(sets.workoutId, session.workoutId));
}
return { ...session, laps: sessionLaps, sets: sessionSets };
}
// ─── Laps ────────────────────────────────────────────────────
export async function saveLap(sessionId: string, lap: LapData) {
const swolf =
lap.strokeCount != null ? lap.strokeCount + Math.round(lap.duration) : null;
await db.insert(laps).values({
id: generateUUID(),
sessionId,
setPosition: lap.setPosition,
repNumber: lap.repNumber,
distance: lap.distance,
duration: lap.duration,
strokeCount: lap.strokeCount ?? null,
heartRate: lap.heartRate ?? null,
swolf,
});
}
export async function saveLaps(sessionId: string, lapsData: LapData[]) {
for (const lap of lapsData) {
await saveLap(sessionId, lap);
}
}
export async function getSessionLaps(sessionId: string) {
return db.select().from(laps).where(eq(laps.sessionId, sessionId));
}
// ─── Delete ──────────────────────────────────────────────────
export async function deleteSession(sessionId: string) {
await db.delete(laps).where(eq(laps.sessionId, sessionId));
await db.delete(workoutSessions).where(eq(workoutSessions.id, sessionId));
}
export async function deleteSessionAndSync(sessionId: string) {
await deleteSession(sessionId);
await WatchConnectivity.sendWorkoutDeleted(sessionId);
}
export async function deleteWorkout(id: string) {
// Verwijder alle sessies die aan dit plan gelinkt zijn
const sessions = await db
.select({ id: workoutSessions.id })
.from(workoutSessions)
.where(eq(workoutSessions.workoutId, id));
for (const session of sessions) {
await db.delete(laps).where(eq(laps.sessionId, session.id));
}
await db.delete(workoutSessions).where(eq(workoutSessions.workoutId, id));
await db.delete(sets).where(eq(sets.workoutId, id));
await db.delete(workouts).where(eq(workouts.id, id));
}
export async function deleteWorkoutAndSync(id: string) {
await deleteWorkout(id);
await WatchConnectivity.sendWorkoutDeleted(id);
}
// ─── Stats ───────────────────────────────────────────────────
export async function getWeeklyStats() {
const now = new Date();
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
return db
.select({
count: sql<number>`count(*)`,
totalDistance: sql<number>`coalesce(sum(${workoutSessions.totalDistance}), 0)`,
totalDuration: sql<number>`coalesce(sum(${workoutSessions.totalDuration}), 0)`,
avgCalories: sql<number>`coalesce(avg(${workoutSessions.calories}), 0)`,
})
.from(workoutSessions)
.where(sql`${workoutSessions.startedAt} >= ${weekAgo.toISOString()}`)
.get();
}
export async function getPersonalBests() {
return db
.select({
distance: laps.distance,
bestTime: sql<number>`min(${laps.duration})`,
bestSwolf: sql<number>`min(${laps.swolf})`,
})
.from(laps)
.groupBy(laps.distance)
.having(sql`${laps.distance} > 0`);
}
export async function getTotalStats() {
return db
.select({
totalWorkouts: sql<number>`count(*)`,
totalDistance: sql<number>`coalesce(sum(${workoutSessions.totalDistance}), 0)`,
totalDuration: sql<number>`coalesce(sum(${workoutSessions.totalDuration}), 0)`,
})
.from(workoutSessions)
.where(sql`${workoutSessions.finishedAt} IS NOT NULL`)
.get();
}