build errors fixed
This commit is contained in:
parent
d3a36b6103
commit
6580564767
2
apps/admin/next-env.d.ts
vendored
2
apps/admin/next-env.d.ts
vendored
@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
import "./.next/dev/types/routes.d.ts";
|
import "./.next/types/routes.d.ts";
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
@ -38,7 +38,7 @@ export async function GET(req: Request) {
|
|||||||
if (!user.gymId) {
|
if (!user.gymId) {
|
||||||
return new NextResponse("Admin gymId not set", { status: 400 });
|
return new NextResponse("Admin gymId not set", { status: 400 });
|
||||||
}
|
}
|
||||||
targetGymId = user.gymId;
|
targetGymId = user.gymId as string;
|
||||||
} else if (user.role === "superAdmin") {
|
} else if (user.role === "superAdmin") {
|
||||||
targetGymId = requestedGymId;
|
targetGymId = requestedGymId;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from "next/server";
|
||||||
import { auth } from '@clerk/nextjs/server'
|
import { auth } from "@clerk/nextjs/server";
|
||||||
import { db, users as usersTable, gyms as gymsTable, eq } from '@fitai/database'
|
import { db, users as usersTable, eq, sql } from "@fitai/database";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/users/gym
|
* 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) {
|
export async function PATCH(req: Request) {
|
||||||
try {
|
try {
|
||||||
const { userId } = await auth()
|
const { userId } = await auth();
|
||||||
if (!userId) return new NextResponse('Unauthorized', { status: 401 })
|
if (!userId) return new NextResponse("Unauthorized", { status: 401 });
|
||||||
|
|
||||||
const body = await req.json().catch(() => null)
|
const body = await req.json().catch(() => null);
|
||||||
if (!body || typeof body !== 'object' || !('gymId' in body)) {
|
if (!body || typeof body !== "object" || !("gymId" in body)) {
|
||||||
return NextResponse.json({ error: 'gymId is required in body (can be null)' }, { status: 400 })
|
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
|
// Ensure user exists
|
||||||
const user = await db.select().from(usersTable).where(eq(usersTable.id, userId)).get()
|
const user = await db
|
||||||
if (!user) return new NextResponse('User not found', { status: 404 })
|
.select()
|
||||||
|
.from(usersTable)
|
||||||
|
.where(eq(usersTable.id, userId))
|
||||||
|
.get();
|
||||||
|
if (!user) return new NextResponse("User not found", { status: 404 });
|
||||||
|
|
||||||
// Validate gym when provided
|
// Validate gym when provided
|
||||||
if (gymId) {
|
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) {
|
if (!gym) {
|
||||||
return NextResponse.json({ error: 'Gym not found' }, { status: 404 })
|
return NextResponse.json({ error: "Gym not found" }, { status: 404 });
|
||||||
}
|
}
|
||||||
if (gym.status !== 'active') {
|
if (gym.status !== "active") {
|
||||||
return NextResponse.json({ error: 'Gym is not active' }, { status: 400 })
|
return NextResponse.json(
|
||||||
|
{ error: "Gym is not active" },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update user's gym selection
|
// Update user's gym selection
|
||||||
await db
|
await db.run(
|
||||||
.update(usersTable)
|
sql`UPDATE users SET gym_id = ${gymId ?? null}, updated_at = ${new Date()} WHERE id = ${userId}`,
|
||||||
.set({
|
);
|
||||||
gymId: gymId ?? null,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
})
|
|
||||||
.where(eq(usersTable.id, userId))
|
|
||||||
.run()
|
|
||||||
|
|
||||||
const updated = await db.select().from(usersTable).where(eq(usersTable.id, userId)).get()
|
const updated = await db
|
||||||
return NextResponse.json(updated)
|
.select()
|
||||||
|
.from(usersTable)
|
||||||
|
.where(eq(usersTable.id, userId))
|
||||||
|
.get();
|
||||||
|
return NextResponse.json(updated);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('PATCH /users/gym error:', error)
|
console.error("PATCH /users/gym error:", error);
|
||||||
return new NextResponse('Internal Server Error', { status: 500 })
|
return new NextResponse("Internal Server Error", { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -304,7 +304,7 @@ export async function PUT(request: NextRequest) {
|
|||||||
role: role ?? existingUser.role,
|
role: role ?? existingUser.role,
|
||||||
phone: phone !== undefined ? phone : existingUser.phone,
|
phone: phone !== undefined ? phone : existingUser.phone,
|
||||||
gymId: gymId !== undefined ? gymId : existingUser.gymId,
|
gymId: gymId !== undefined ? gymId : existingUser.gymId,
|
||||||
} as any);
|
});
|
||||||
|
|
||||||
return NextResponse.json({ success: true });
|
return NextResponse.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -86,7 +86,7 @@ export async function POST(req: Request) {
|
|||||||
| "trainer"
|
| "trainer"
|
||||||
| "client"
|
| "client"
|
||||||
| "generalUser") || "client";
|
| "generalUser") || "client";
|
||||||
const gymId = (public_metadata?.gymId as string | null) ?? null;
|
let gymId = (public_metadata?.gymId as string | null) ?? null;
|
||||||
const inviterUserId =
|
const inviterUserId =
|
||||||
(public_metadata?.inviterUserId as string | undefined) ?? undefined;
|
(public_metadata?.inviterUserId as string | undefined) ?? undefined;
|
||||||
const roleAssigned =
|
const roleAssigned =
|
||||||
@ -147,8 +147,8 @@ export async function POST(req: Request) {
|
|||||||
inviterUserId
|
inviterUserId
|
||||||
) {
|
) {
|
||||||
const inviterGymRow = db
|
const inviterGymRow = db
|
||||||
.prepare("SELECT gymId FROM users WHERE id = ?")
|
.prepare("SELECT gym_id FROM users WHERE id = ?")
|
||||||
.get(inviterUserId) as { gymId?: string } | undefined;
|
.get(inviterUserId) as { gym_id?: string } | undefined;
|
||||||
|
|
||||||
if (inviterGymRow?.gym_id) {
|
if (inviterGymRow?.gym_id) {
|
||||||
const inheritStmt = db.prepare(`
|
const inheritStmt = db.prepare(`
|
||||||
@ -194,7 +194,7 @@ export async function POST(req: Request) {
|
|||||||
| "trainer"
|
| "trainer"
|
||||||
| "client"
|
| "client"
|
||||||
| "generalUser") || "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
|
// Update user in database
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
@ -225,7 +225,7 @@ export async function POST(req: Request) {
|
|||||||
.prepare("SELECT gym_id FROM users WHERE id = ?")
|
.prepare("SELECT gym_id FROM users WHERE id = ?")
|
||||||
.get(inviterUserId) as { gym_id?: string } | undefined;
|
.get(inviterUserId) as { gym_id?: string } | undefined;
|
||||||
|
|
||||||
if (inviterGymRow?.gymId) {
|
if (inviterGymRow?.gym_id) {
|
||||||
const inheritStmt = db.prepare(`
|
const inheritStmt = db.prepare(`
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET gym_id = ?, updated_at = ?
|
SET gym_id = ?, updated_at = ?
|
||||||
|
|||||||
@ -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
|
// Database Entity Types
|
||||||
export interface User extends SharedUser {
|
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;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { Client, FitnessProfile, Attendance, Recommendation, FitnessGoal };
|
export type { Client, FitnessProfile, Attendance, Recommendation, FitnessGoal };
|
||||||
|
|
||||||
|
|
||||||
// Database Interface - allows us to swap implementations
|
// Database Interface - allows us to swap implementations
|
||||||
export interface IDatabase {
|
export interface IDatabase {
|
||||||
// Connection management
|
// Connection management
|
||||||
@ -58,7 +66,10 @@ export interface IDatabase {
|
|||||||
|
|
||||||
// Recommendation operations
|
// Recommendation operations
|
||||||
createRecommendation(
|
createRecommendation(
|
||||||
recommendation: Omit<Recommendation, "createdAt" | "approvedAt" | "approvedBy">,
|
recommendation: Omit<
|
||||||
|
Recommendation,
|
||||||
|
"createdAt" | "approvedAt" | "approvedBy"
|
||||||
|
>,
|
||||||
): Promise<Recommendation>;
|
): Promise<Recommendation>;
|
||||||
getRecommendationsByUserId(userId: string): Promise<Recommendation[]>;
|
getRecommendationsByUserId(userId: string): Promise<Recommendation[]>;
|
||||||
getAllRecommendations(): Promise<Recommendation[]>;
|
getAllRecommendations(): Promise<Recommendation[]>;
|
||||||
@ -70,16 +81,22 @@ export interface IDatabase {
|
|||||||
|
|
||||||
// Fitness Goals operations
|
// Fitness Goals operations
|
||||||
createFitnessGoal(
|
createFitnessGoal(
|
||||||
goal: Omit<FitnessGoal, "createdAt" | "updatedAt">
|
goal: Omit<FitnessGoal, "createdAt" | "updatedAt">,
|
||||||
): Promise<FitnessGoal>;
|
): Promise<FitnessGoal>;
|
||||||
getFitnessGoalById(id: string): Promise<FitnessGoal | null>;
|
getFitnessGoalById(id: string): Promise<FitnessGoal | null>;
|
||||||
getFitnessGoalsByUserId(userId: string, status?: string): Promise<FitnessGoal[]>;
|
getFitnessGoalsByUserId(
|
||||||
|
userId: string,
|
||||||
|
status?: string,
|
||||||
|
): Promise<FitnessGoal[]>;
|
||||||
updateFitnessGoal(
|
updateFitnessGoal(
|
||||||
id: string,
|
id: string,
|
||||||
updates: Partial<FitnessGoal>
|
updates: Partial<FitnessGoal>,
|
||||||
): Promise<FitnessGoal | null>;
|
): Promise<FitnessGoal | null>;
|
||||||
deleteFitnessGoal(id: string): Promise<boolean>;
|
deleteFitnessGoal(id: string): Promise<boolean>;
|
||||||
updateGoalProgress(id: string, currentValue: number): Promise<FitnessGoal | null>;
|
updateGoalProgress(
|
||||||
|
id: string,
|
||||||
|
currentValue: number,
|
||||||
|
): Promise<FitnessGoal | null>;
|
||||||
completeGoal(id: string): Promise<FitnessGoal | null>;
|
completeGoal(id: string): Promise<FitnessGoal | null>;
|
||||||
|
|
||||||
// Dashboard operations
|
// Dashboard operations
|
||||||
|
|||||||
@ -1,62 +1,72 @@
|
|||||||
import { currentUser } from '@clerk/nextjs/server'
|
import { currentUser } from "@clerk/nextjs/server";
|
||||||
import { IDatabase } from './database/types'
|
import type { IDatabase, User } from "./database/types";
|
||||||
|
|
||||||
export async function ensureUserSynced(userId: string, db: IDatabase) {
|
export async function ensureUserSynced(
|
||||||
const existingUser = await db.getUserById(userId)
|
userId: string,
|
||||||
if (existingUser) return existingUser
|
db: IDatabase,
|
||||||
|
): Promise<User | null> {
|
||||||
|
const existingUser = await db.getUserById(userId);
|
||||||
|
if (existingUser) return existingUser;
|
||||||
|
|
||||||
console.log('User not found in DB by ID, checking Clerk:', userId)
|
console.log("User not found in DB by ID, checking Clerk:", userId);
|
||||||
const clerkUser = await currentUser()
|
const clerkUser = await currentUser();
|
||||||
|
|
||||||
if (!clerkUser || clerkUser.id !== userId) {
|
if (!clerkUser || clerkUser.id !== userId) {
|
||||||
// If we can't get the user from Clerk (e.g. running locally without full auth sync),
|
// 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.
|
// we might want to fail gracefully or throw.
|
||||||
// For now, throw to be safe.
|
// For now, throw to be safe.
|
||||||
throw new Error('Could not fetch Clerk user details')
|
throw new Error("Could not fetch Clerk user details");
|
||||||
}
|
}
|
||||||
|
|
||||||
const email = clerkUser.emailAddresses[0]?.emailAddress
|
const email = clerkUser.emailAddresses[0]?.emailAddress;
|
||||||
if (!email) throw new Error('User has no email')
|
if (!email) throw new Error("User has no email");
|
||||||
|
|
||||||
// Check if user exists by email (e.g. seeded user)
|
// Check if user exists by email (e.g. seeded user)
|
||||||
const existingByEmail = await db.getUserByEmail(email)
|
const existingByEmail = await db.getUserByEmail(email);
|
||||||
if (existingByEmail) {
|
if (existingByEmail) {
|
||||||
console.log('User found by email but ID mismatch. Migrating ID...', {
|
console.log("User found by email but ID mismatch. Migrating ID...", {
|
||||||
oldId: existingByEmail.id,
|
oldId: existingByEmail.id,
|
||||||
newId: userId
|
newId: userId,
|
||||||
})
|
});
|
||||||
|
|
||||||
// Update the ID to match Clerk ID
|
// 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
|
// 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.
|
// 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),
|
// 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.
|
// we should add a method to IDatabase or use a workaround.
|
||||||
// Actually, `SQLiteDatabase` is what we have at runtime.
|
// Actually, `SQLiteDatabase` is what we have at runtime.
|
||||||
// Let's assume we can cast it or add `updateUserId` to the interface.
|
// 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.
|
// 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 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.
|
// 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).
|
// 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.
|
// But since it's a seeded super admin with no data, it's fine.
|
||||||
// However, if they had clients, we'd lose the link.
|
// However, if they had clients, we'd lose the link.
|
||||||
|
|
||||||
// Let's add `migrateUserId(oldId, newId)` to IDatabase.
|
// Let's add `migrateUserId(oldId, newId)` to IDatabase.
|
||||||
await db.migrateUserId(existingByEmail.id, userId)
|
await db.migrateUserId(existingByEmail.id, userId);
|
||||||
return db.getUserById(userId)
|
return db.getUserById(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Creating new user from Clerk data:', userId)
|
console.log("Creating new user from Clerk data:", userId);
|
||||||
const user = await db.createUser({
|
const user = await db.createUser({
|
||||||
id: userId,
|
id: userId,
|
||||||
email,
|
email,
|
||||||
firstName: clerkUser.firstName || '',
|
firstName: clerkUser.firstName || "",
|
||||||
lastName: clerkUser.lastName || '',
|
lastName: clerkUser.lastName || "",
|
||||||
password: '', // Managed by Clerk
|
password: "", // Managed by Clerk
|
||||||
phone: clerkUser.phoneNumbers[0]?.phoneNumber || undefined,
|
phone: clerkUser.phoneNumbers[0]?.phoneNumber || undefined,
|
||||||
role: (clerkUser.publicMetadata.role as 'admin' | 'client' | 'superAdmin') || 'client'
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ export interface User {
|
|||||||
lastName: string;
|
lastName: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
role: "superAdmin" | "admin" | "trainer" | "client" | "generalUser";
|
role: "superAdmin" | "admin" | "trainer" | "client";
|
||||||
gymId?: string;
|
gymId?: string;
|
||||||
imageUrl?: string;
|
imageUrl?: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user