From 65805647676c345e51d2fd9e394115c22fd6142a Mon Sep 17 00:00:00 2001 From: echo Date: Thu, 18 Dec 2025 17:19:11 +0100 Subject: [PATCH] build errors fixed --- apps/admin/next-env.d.ts | 2 +- .../admin/src/app/api/admin/trainers/route.ts | 2 +- apps/admin/src/app/api/users/gym/route.ts | 66 ++++++----- apps/admin/src/app/api/users/route.ts | 2 +- apps/admin/src/app/api/webhooks/route.ts | 10 +- apps/admin/src/lib/database/types.ts | 31 +++-- apps/admin/src/lib/sync-user.ts | 110 ++++++++++-------- packages/shared/src/types/index.ts | 2 +- 8 files changed, 132 insertions(+), 93 deletions(-) diff --git a/apps/admin/next-env.d.ts b/apps/admin/next-env.d.ts index c4b7818..9edff1c 100644 --- a/apps/admin/next-env.d.ts +++ b/apps/admin/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/admin/src/app/api/admin/trainers/route.ts b/apps/admin/src/app/api/admin/trainers/route.ts index d112055..1db2fbc 100644 --- a/apps/admin/src/app/api/admin/trainers/route.ts +++ b/apps/admin/src/app/api/admin/trainers/route.ts @@ -38,7 +38,7 @@ export async function GET(req: Request) { if (!user.gymId) { return new NextResponse("Admin gymId not set", { status: 400 }); } - targetGymId = user.gymId; + targetGymId = user.gymId as string; } else if (user.role === "superAdmin") { targetGymId = requestedGymId; } diff --git a/apps/admin/src/app/api/users/gym/route.ts b/apps/admin/src/app/api/users/gym/route.ts index 98ae14c..93efe00 100644 --- a/apps/admin/src/app/api/users/gym/route.ts +++ b/apps/admin/src/app/api/users/gym/route.ts @@ -1,6 +1,6 @@ -import { NextResponse } from 'next/server' -import { auth } from '@clerk/nextjs/server' -import { db, users as usersTable, gyms as gymsTable, eq } from '@fitai/database' +import { NextResponse } from "next/server"; +import { auth } from "@clerk/nextjs/server"; +import { db, users as usersTable, eq, sql } from "@fitai/database"; /** * PATCH /api/users/gym @@ -11,45 +11,57 @@ import { db, users as usersTable, gyms as gymsTable, eq } from '@fitai/database' */ export async function PATCH(req: Request) { try { - const { userId } = await auth() - if (!userId) return new NextResponse('Unauthorized', { status: 401 }) + const { userId } = await auth(); + if (!userId) return new NextResponse("Unauthorized", { status: 401 }); - const body = await req.json().catch(() => null) - if (!body || typeof body !== 'object' || !('gymId' in body)) { - return NextResponse.json({ error: 'gymId is required in body (can be null)' }, { status: 400 }) + const body = await req.json().catch(() => null); + if (!body || typeof body !== "object" || !("gymId" in body)) { + return NextResponse.json( + { error: "gymId is required in body (can be null)" }, + { status: 400 }, + ); } - const gymId = body.gymId === null ? null : String(body.gymId) + const gymId = body.gymId === null ? null : String(body.gymId); // Ensure user exists - const user = await db.select().from(usersTable).where(eq(usersTable.id, userId)).get() - if (!user) return new NextResponse('User not found', { status: 404 }) + const user = await db + .select() + .from(usersTable) + .where(eq(usersTable.id, userId)) + .get(); + if (!user) return new NextResponse("User not found", { status: 404 }); // Validate gym when provided if (gymId) { - const gym = await db.select().from(gymsTable).where(eq(gymsTable.id, gymId)).get() + const rows = await db.all( + sql`SELECT status FROM gyms WHERE id = ${gymId} LIMIT 1`, + ); + const gym = rows?.[0] as { status?: string } | undefined; if (!gym) { - return NextResponse.json({ error: 'Gym not found' }, { status: 404 }) + return NextResponse.json({ error: "Gym not found" }, { status: 404 }); } - if (gym.status !== 'active') { - return NextResponse.json({ error: 'Gym is not active' }, { status: 400 }) + if (gym.status !== "active") { + return NextResponse.json( + { error: "Gym is not active" }, + { status: 400 }, + ); } } // Update user's gym selection - await db - .update(usersTable) - .set({ - gymId: gymId ?? null, - updatedAt: new Date(), - }) - .where(eq(usersTable.id, userId)) - .run() + await db.run( + sql`UPDATE users SET gym_id = ${gymId ?? null}, updated_at = ${new Date()} WHERE id = ${userId}`, + ); - const updated = await db.select().from(usersTable).where(eq(usersTable.id, userId)).get() - return NextResponse.json(updated) + const updated = await db + .select() + .from(usersTable) + .where(eq(usersTable.id, userId)) + .get(); + return NextResponse.json(updated); } catch (error) { - console.error('PATCH /users/gym error:', error) - return new NextResponse('Internal Server Error', { status: 500 }) + console.error("PATCH /users/gym error:", error); + return new NextResponse("Internal Server Error", { status: 500 }); } } diff --git a/apps/admin/src/app/api/users/route.ts b/apps/admin/src/app/api/users/route.ts index 2737e6f..28aac92 100644 --- a/apps/admin/src/app/api/users/route.ts +++ b/apps/admin/src/app/api/users/route.ts @@ -304,7 +304,7 @@ export async function PUT(request: NextRequest) { role: role ?? existingUser.role, phone: phone !== undefined ? phone : existingUser.phone, gymId: gymId !== undefined ? gymId : existingUser.gymId, - } as any); + }); return NextResponse.json({ success: true }); } catch (error) { diff --git a/apps/admin/src/app/api/webhooks/route.ts b/apps/admin/src/app/api/webhooks/route.ts index e67feae..4e4282e 100644 --- a/apps/admin/src/app/api/webhooks/route.ts +++ b/apps/admin/src/app/api/webhooks/route.ts @@ -86,7 +86,7 @@ export async function POST(req: Request) { | "trainer" | "client" | "generalUser") || "client"; - const gymId = (public_metadata?.gymId as string | null) ?? null; + let gymId = (public_metadata?.gymId as string | null) ?? null; const inviterUserId = (public_metadata?.inviterUserId as string | undefined) ?? undefined; const roleAssigned = @@ -147,8 +147,8 @@ export async function POST(req: Request) { inviterUserId ) { const inviterGymRow = db - .prepare("SELECT gymId FROM users WHERE id = ?") - .get(inviterUserId) as { gymId?: string } | undefined; + .prepare("SELECT gym_id FROM users WHERE id = ?") + .get(inviterUserId) as { gym_id?: string } | undefined; if (inviterGymRow?.gym_id) { const inheritStmt = db.prepare(` @@ -194,7 +194,7 @@ export async function POST(req: Request) { | "trainer" | "client" | "generalUser") || "client"; - const gymId = (public_metadata?.gymId as string | null) ?? null; + let gymId = (public_metadata?.gymId as string | null) ?? null; // Update user in database const now = new Date().toISOString(); @@ -225,7 +225,7 @@ export async function POST(req: Request) { .prepare("SELECT gym_id FROM users WHERE id = ?") .get(inviterUserId) as { gym_id?: string } | undefined; - if (inviterGymRow?.gymId) { + if (inviterGymRow?.gym_id) { const inheritStmt = db.prepare(` UPDATE users SET gym_id = ?, updated_at = ? diff --git a/apps/admin/src/lib/database/types.ts b/apps/admin/src/lib/database/types.ts index 0804d2d..174a95d 100644 --- a/apps/admin/src/lib/database/types.ts +++ b/apps/admin/src/lib/database/types.ts @@ -1,13 +1,21 @@ -import { User as SharedUser, Client, FitnessProfile, Attendance, Recommendation, FitnessGoal } from "@fitai/shared"; +import { + User as SharedUser, + Client, + FitnessProfile, + Attendance, + Recommendation, + FitnessGoal, +} from "@fitai/shared"; // Database Entity Types export interface User extends SharedUser { + // Explicitly include gymId so server imports can rely on it even if shared types lag behind build + gymId?: string; password: string; } export type { Client, FitnessProfile, Attendance, Recommendation, FitnessGoal }; - // Database Interface - allows us to swap implementations export interface IDatabase { // Connection management @@ -58,7 +66,10 @@ export interface IDatabase { // Recommendation operations createRecommendation( - recommendation: Omit, + recommendation: Omit< + Recommendation, + "createdAt" | "approvedAt" | "approvedBy" + >, ): Promise; getRecommendationsByUserId(userId: string): Promise; getAllRecommendations(): Promise; @@ -70,16 +81,22 @@ export interface IDatabase { // Fitness Goals operations createFitnessGoal( - goal: Omit + goal: Omit, ): Promise; getFitnessGoalById(id: string): Promise; - getFitnessGoalsByUserId(userId: string, status?: string): Promise; + getFitnessGoalsByUserId( + userId: string, + status?: string, + ): Promise; updateFitnessGoal( id: string, - updates: Partial + updates: Partial, ): Promise; deleteFitnessGoal(id: string): Promise; - updateGoalProgress(id: string, currentValue: number): Promise; + updateGoalProgress( + id: string, + currentValue: number, + ): Promise; completeGoal(id: string): Promise; // Dashboard operations diff --git a/apps/admin/src/lib/sync-user.ts b/apps/admin/src/lib/sync-user.ts index 410dcfd..4965b42 100644 --- a/apps/admin/src/lib/sync-user.ts +++ b/apps/admin/src/lib/sync-user.ts @@ -1,62 +1,72 @@ -import { currentUser } from '@clerk/nextjs/server' -import { IDatabase } from './database/types' +import { currentUser } from "@clerk/nextjs/server"; +import type { IDatabase, User } from "./database/types"; -export async function ensureUserSynced(userId: string, db: IDatabase) { - const existingUser = await db.getUserById(userId) - if (existingUser) return existingUser +export async function ensureUserSynced( + userId: string, + db: IDatabase, +): Promise { + const existingUser = await db.getUserById(userId); + if (existingUser) return existingUser; - console.log('User not found in DB by ID, checking Clerk:', userId) - const clerkUser = await currentUser() + console.log("User not found in DB by ID, checking Clerk:", userId); + const clerkUser = await currentUser(); - if (!clerkUser || clerkUser.id !== userId) { - // If we can't get the user from Clerk (e.g. running locally without full auth sync), - // we might want to fail gracefully or throw. - // For now, throw to be safe. - throw new Error('Could not fetch Clerk user details') - } + if (!clerkUser || clerkUser.id !== userId) { + // If we can't get the user from Clerk (e.g. running locally without full auth sync), + // we might want to fail gracefully or throw. + // For now, throw to be safe. + throw new Error("Could not fetch Clerk user details"); + } - const email = clerkUser.emailAddresses[0]?.emailAddress - if (!email) throw new Error('User has no email') + const email = clerkUser.emailAddresses[0]?.emailAddress; + if (!email) throw new Error("User has no email"); - // Check if user exists by email (e.g. seeded user) - const existingByEmail = await db.getUserByEmail(email) - if (existingByEmail) { - console.log('User found by email but ID mismatch. Migrating ID...', { - oldId: existingByEmail.id, - newId: userId - }) + // Check if user exists by email (e.g. seeded user) + const existingByEmail = await db.getUserByEmail(email); + if (existingByEmail) { + console.log("User found by email but ID mismatch. Migrating ID...", { + oldId: existingByEmail.id, + newId: userId, + }); - // Update the ID to match Clerk ID - // We need to do this manually via SQL because IDatabase interface might not expose a direct ID update method easily - // But we can use a raw query if we had access, or add a method. - // Since we don't have direct access to `db.db` here (it's hidden behind IDatabase), - // we should add a method to IDatabase or use a workaround. - // Actually, `SQLiteDatabase` is what we have at runtime. - // Let's assume we can cast it or add `updateUserId` to the interface. + // Update the ID to match Clerk ID + // We need to do this manually via SQL because IDatabase interface might not expose a direct ID update method easily + // But we can use a raw query if we had access, or add a method. + // Since we don't have direct access to `db.db` here (it's hidden behind IDatabase), + // we should add a method to IDatabase or use a workaround. + // Actually, `SQLiteDatabase` is what we have at runtime. + // Let's assume we can cast it or add `updateUserId` to the interface. - // For now, let's try to update it using `updateUser` but `updateUser` usually updates fields based on ID. - // We can't update the ID itself using `updateUser(id, { id: newId })` because `updateUser` implementation filters out `id` from updates. + // For now, let's try to update it using `updateUser` but `updateUser` usually updates fields based on ID. + // We can't update the ID itself using `updateUser(id, { id: newId })` because `updateUser` implementation filters out `id` from updates. - // We need to add a method to migrate user ID in IDatabase. - // Or, strictly for this prototype, we can delete the old user and create a new one (DATA LOSS RISK). - // But since it's a seeded super admin with no data, it's fine. - // However, if they had clients, we'd lose the link. + // We need to add a method to migrate user ID in IDatabase. + // Or, strictly for this prototype, we can delete the old user and create a new one (DATA LOSS RISK). + // But since it's a seeded super admin with no data, it's fine. + // However, if they had clients, we'd lose the link. - // Let's add `migrateUserId(oldId, newId)` to IDatabase. - await db.migrateUserId(existingByEmail.id, userId) - return db.getUserById(userId) - } + // Let's add `migrateUserId(oldId, newId)` to IDatabase. + await db.migrateUserId(existingByEmail.id, userId); + return db.getUserById(userId); + } - console.log('Creating new user from Clerk data:', userId) - const user = await db.createUser({ - id: userId, - email, - firstName: clerkUser.firstName || '', - lastName: clerkUser.lastName || '', - password: '', // Managed by Clerk - phone: clerkUser.phoneNumbers[0]?.phoneNumber || undefined, - role: (clerkUser.publicMetadata.role as 'admin' | 'client' | 'superAdmin') || 'client' - }) + console.log("Creating new user from Clerk data:", userId); + const user = await db.createUser({ + id: userId, + email, + firstName: clerkUser.firstName || "", + lastName: clerkUser.lastName || "", + password: "", // Managed by Clerk + phone: clerkUser.phoneNumbers[0]?.phoneNumber || undefined, + gymId: (clerkUser.publicMetadata.gymId as string | undefined) || undefined, + role: ((): any => { + const r = clerkUser.publicMetadata.role as string | undefined; + return r && + ["superAdmin", "admin", "trainer", "client", "generalUser"].includes(r) + ? r + : "client"; + })(), + }); - return user + return user; } diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index a68c5bb..c22cc82 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -5,7 +5,7 @@ export interface User { lastName: string; password?: string; phone?: string; - role: "superAdmin" | "admin" | "trainer" | "client" | "generalUser"; + role: "superAdmin" | "admin" | "trainer" | "client"; gymId?: string; imageUrl?: string; createdAt: Date;