import { sqliteTable, text, integer, real, index, unique, uniqueIndex, } from "drizzle-orm/sqlite-core"; export const users = sqliteTable( "users", { id: text("id").primaryKey(), email: text("email").notNull().unique(), firstName: text("first_name").notNull(), lastName: text("last_name").notNull(), password: text("password"), // Optional - Clerk handles authentication role: text("role", { enum: ["superAdmin", "admin", "trainer", "client"], }) .notNull() .default("client"), phone: text("phone"), gymId: text("gym_id"), // FK reference added after gyms table expoPushToken: text("expo_push_token"), // For push notifications deviceType: text("device_type", { enum: ["ios", "android"] }), // Device platform createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), updatedAt: integer("updated_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ emailIdx: index("users_email_idx").on(table.email), gymIdIdx: index("users_gym_id_idx").on(table.gymId), roleIdx: index("users_role_idx").on(table.role), expoPushTokenIdx: index("users_expo_push_token_idx").on( table.expoPushToken, ), }), ); export const gyms = sqliteTable( "gyms", { id: text("id").primaryKey(), name: text("name").notNull(), location: text("location"), status: text("status", { enum: ["active", "inactive"] }) .notNull() .default("active"), adminUserId: text("admin_user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), createdAt: integer("created_at", { mode: "timestamp_ms" }) .notNull() .$defaultFn(() => new Date()), updatedAt: integer("updated_at", { mode: "timestamp_ms" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ adminUserIdIdx: index("gyms_admin_user_id_idx").on(table.adminUserId), statusIdx: index("gyms_status_idx").on(table.status), }), ); export const clients = sqliteTable( "clients", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .unique() .references(() => users.id, { onDelete: "cascade" }), membershipType: text("membership_type", { enum: ["basic", "premium", "vip"], }) .notNull() .default("basic"), membershipStatus: text("membership_status", { enum: ["active", "inactive", "suspended"], }) .notNull() .default("active"), joinDate: integer("join_date", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), lastVisit: integer("last_visit", { mode: "timestamp" }), emergencyContactName: text("emergency_contact_name"), emergencyContactPhone: text("emergency_contact_phone"), emergencyContactRelationship: text("emergency_contact_relationship"), createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), updatedAt: integer("updated_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ userIdIdx: index("clients_user_id_idx").on(table.userId), membershipStatusIdx: index("clients_membership_status_idx").on( table.membershipStatus, ), }), ); export const payments = sqliteTable( "payments", { id: text("id").primaryKey(), clientId: text("client_id") .notNull() .references(() => clients.id, { onDelete: "cascade" }), amount: real("amount").notNull(), currency: text("currency").notNull().default("USD"), status: text("status", { enum: ["pending", "completed", "failed", "refunded"], }) .notNull() .default("pending"), paymentMethod: text("payment_method", { enum: ["cash", "card", "bank_transfer"], }).notNull(), dueDate: integer("due_date", { mode: "timestamp" }).notNull(), paidAt: integer("paid_at", { mode: "timestamp" }), description: text("description").notNull(), createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), updatedAt: integer("updated_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ clientIdIdx: index("payments_client_id_idx").on(table.clientId), statusIdx: index("payments_status_idx").on(table.status), dueDateIdx: index("payments_due_date_idx").on(table.dueDate), }), ); export const attendance = sqliteTable( "attendance", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), checkInTime: integer("check_in_time", { mode: "timestamp" }).notNull(), checkOutTime: integer("check_out_time", { mode: "timestamp" }), type: text("type", { enum: ["gym", "class", "personal_training"] }) .notNull() .default("gym"), notes: text("notes"), createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ userIdIdx: index("attendance_user_id_idx").on(table.userId), checkInTimeIdx: index("attendance_check_in_time_idx").on(table.checkInTime), // Composite index for common query: get user's attendance history userCheckInIdx: index("attendance_user_check_in_idx").on( table.userId, table.checkInTime, ), }), ); export const notifications = sqliteTable( "notifications", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), title: text("title").notNull(), message: text("message").notNull(), type: text("type", { enum: ["payment_reminder", "attendance", "promotion", "system"], }).notNull(), read: integer("read", { mode: "boolean" }).notNull().default(false), createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ userIdIdx: index("notifications_user_id_idx").on(table.userId), readIdx: index("notifications_read_idx").on(table.read), // Composite index for common query: get user's unread notifications userReadIdx: index("notifications_user_read_idx").on( table.userId, table.read, ), }), ); export const fitnessProfiles = sqliteTable( "fitness_profiles", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .unique() .references(() => users.id, { onDelete: "cascade" }), height: real("height"), // in cm weight: real("weight"), // in kg age: integer("age"), gender: text("gender", { enum: ["male", "female", "other", "prefer_not_to_say"], }), fitnessGoals: text("fitness_goals", { mode: "json" }).$type(), activityLevel: text("activity_level", { enum: [ "sedentary", "lightly_active", "moderately_active", "very_active", "extremely_active", ], }), medicalConditions: text("medical_conditions"), allergies: text("allergies"), injuries: text("injuries"), createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), updatedAt: integer("updated_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ userIdIdx: index("fitness_profiles_user_id_idx").on(table.userId), }), ); export const fitnessGoals = sqliteTable( "fitness_goals", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), fitnessProfileId: text("fitness_profile_id").references( () => fitnessProfiles.id, { onDelete: "cascade" }, ), // Goal details goalType: text("goal_type", { enum: [ "weight_target", "strength_milestone", "endurance_target", "flexibility_goal", "habit_building", "custom", ], }).notNull(), title: text("title").notNull(), description: text("description"), // Measurable targets targetValue: real("target_value"), // e.g., 70 (kg), 100 (kg bench press) currentValue: real("current_value"), // Current progress unit: text("unit"), // kg, km, reps, etc. // Timeline startDate: integer("start_date", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), targetDate: integer("target_date", { mode: "timestamp" }), completedDate: integer("completed_date", { mode: "timestamp" }), // Status tracking status: text("status", { enum: ["active", "completed", "abandoned", "paused"], }) .notNull() .default("active"), progress: real("progress").default(0), // 0-100 percentage // Metadata priority: text("priority", { enum: ["low", "medium", "high"], }).default("medium"), notes: text("notes"), createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), updatedAt: integer("updated_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ userIdIdx: index("fitness_goals_user_id_idx").on(table.userId), statusIdx: index("fitness_goals_status_idx").on(table.status), // Composite index for common query: get user's active goals userStatusIdx: index("fitness_goals_user_status_idx").on( table.userId, table.status, ), }), ); // Removed local invitations table; Clerk invitations are the source of truth export const trainerClients = sqliteTable( "trainer_clients", { id: text("id").primaryKey(), trainerUserId: text("trainer_user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), clientUserId: text("client_user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), gymId: text("gym_id") .notNull() .references(() => gyms.id, { onDelete: "cascade" }), createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ trainerIdIdx: index("trainer_clients_trainer_id_idx").on( table.trainerUserId, ), clientIdIdx: index("trainer_clients_client_id_idx").on(table.clientUserId), gymIdIdx: index("trainer_clients_gym_id_idx").on(table.gymId), // Composite unique constraint: prevent duplicate trainer-client assignments trainerClientUnique: unique("trainer_client_unique").on( table.trainerUserId, table.clientUserId, ), }), ); export const recommendations = sqliteTable( "recommendations", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), fitnessProfileId: text("fitness_profile_id") .notNull() .references(() => fitnessProfiles.id, { onDelete: "cascade" }), recommendationText: text("recommendation_text").notNull(), activityPlan: text("activity_plan").notNull(), dietPlan: text("diet_plan").notNull(), status: text("status", { enum: ["pending", "approved", "rejected"], }) .notNull() .default("pending"), generatedAt: integer("generated_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), approvedAt: integer("approved_at", { mode: "timestamp" }), approvedBy: text("approved_by"), // User ID of admin/trainer createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), updatedAt: integer("updated_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ userIdIdx: index("recommendations_user_id_idx").on(table.userId), statusIdx: index("recommendations_status_idx").on(table.status), fitnessProfileIdIdx: index("recommendations_fitness_profile_id_idx").on( table.fitnessProfileId, ), }), ); // Daily nutrition tracking table export const dailyNutrition = sqliteTable( "daily_nutrition", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), date: text("date").notNull(), // YYYY-MM-DD format totalCalories: real("total_calories").notNull().default(0), calorieGoal: real("calorie_goal").notNull().default(2000), meals: text("meals", { mode: "json" }).$type< Array<{ type: "breakfast" | "lunch" | "dinner" | "snack"; name: string; calories: number; time?: string; }> >(), createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), updatedAt: integer("updated_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ userIdIdx: index("daily_nutrition_user_id_idx").on(table.userId), dateIdx: index("daily_nutrition_date_idx").on(table.date), userDateIdx: uniqueIndex("daily_nutrition_user_date_idx").on( table.userId, table.date, ), }), ); // Individual meal entries table export const mealEntries = sqliteTable( "meal_entries", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), dailyNutritionId: text("daily_nutrition_id").references( () => dailyNutrition.id, { onDelete: "cascade" }, ), mealType: text("meal_type", { enum: ["breakfast", "lunch", "dinner", "snack"], }).notNull(), foodName: text("food_name").notNull(), calories: real("calories").notNull(), protein: real("protein"), // grams (optional) carbs: real("carbs"), // grams (optional) fats: real("fats"), // grams (optional) timestamp: integer("timestamp", { mode: "timestamp" }).notNull(), createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ userIdIdx: index("meal_entries_user_id_idx").on(table.userId), timestampIdx: index("meal_entries_timestamp_idx").on(table.timestamp), dailyNutritionIdIdx: index("meal_entries_daily_nutrition_id_idx").on( table.dailyNutritionId, ), }), ); // Daily hydration tracking table export const dailyHydration = sqliteTable( "daily_hydration", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), date: text("date").notNull(), // YYYY-MM-DD format totalWater: real("total_water").notNull().default(0), // ml waterGoal: real("water_goal").notNull().default(2000), // ml entries: text("entries", { mode: "json" }).$type< Array<{ amount: number; time: string }> >(), createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), updatedAt: integer("updated_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ userIdIdx: index("daily_hydration_user_id_idx").on(table.userId), dateIdx: index("daily_hydration_date_idx").on(table.date), userDateIdx: uniqueIndex("daily_hydration_user_date_idx").on( table.userId, table.date, ), }), ); // Fitness profile history tracking table export const fitnessProfileHistory = sqliteTable( "fitness_profile_history", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), fitnessProfileId: text("fitness_profile_id") .notNull() .references(() => fitnessProfiles.id, { onDelete: "cascade" }), changeType: text("change_type", { enum: [ "weight", "height", "age", "activity_level", "goals", "medical", "other", ], }).notNull(), fieldName: text("field_name").notNull(), // "weight", "height", etc. previousValue: text("previous_value"), // JSON string for flexibility newValue: text("new_value"), // JSON string changedAt: integer("changed_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ userIdIdx: index("fitness_profile_history_user_id_idx").on(table.userId), changedAtIdx: index("fitness_profile_history_changed_at_idx").on( table.changedAt, ), userChangedIdx: index("fitness_profile_history_user_changed_idx").on( table.userId, table.changedAt, ), changeTypeIdx: index("fitness_profile_history_change_type_idx").on( table.changeType, ), }), ); // Trainer-client assignment table export const trainerClientAssignments = sqliteTable( "trainer_client_assignments", { id: text("id").primaryKey(), trainerId: text("trainer_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), clientId: text("client_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), assignedAt: integer("assigned_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), assignedBy: text("assigned_by").references(() => users.id), isActive: integer("is_active", { mode: "boolean" }).notNull().default(true), createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), updatedAt: integer("updated_at", { mode: "timestamp" }) .notNull() .$defaultFn(() => new Date()), }, (table) => ({ trainerIdx: index("trainer_client_assignments_trainer_idx").on( table.trainerId, ), clientIdx: index("trainer_client_assignments_client_idx").on( table.clientId, ), trainerClientIdx: uniqueIndex( "trainer_client_assignments_trainer_client_idx", ).on(table.trainerId, table.clientId), isActiveIdx: index("trainer_client_assignments_is_active_idx").on( table.isActive, ), }), ); export type User = typeof users.$inferSelect; export type NewUser = typeof users.$inferInsert; export type Client = typeof clients.$inferSelect; export type NewClient = typeof clients.$inferInsert; export type Payment = typeof payments.$inferSelect; export type NewPayment = typeof payments.$inferInsert; export type Attendance = typeof attendance.$inferSelect; export type NewAttendance = typeof attendance.$inferInsert; export type Notification = typeof notifications.$inferSelect; export type NewNotification = typeof notifications.$inferInsert; export type FitnessProfile = typeof fitnessProfiles.$inferSelect; export type NewFitnessProfile = typeof fitnessProfiles.$inferInsert; export type FitnessGoal = typeof fitnessGoals.$inferSelect; export type NewFitnessGoal = typeof fitnessGoals.$inferInsert; export type Recommendation = typeof recommendations.$inferSelect; export type NewRecommendation = typeof recommendations.$inferInsert; export type DailyNutrition = typeof dailyNutrition.$inferSelect; export type NewDailyNutrition = typeof dailyNutrition.$inferInsert; export type MealEntry = typeof mealEntries.$inferSelect; export type NewMealEntry = typeof mealEntries.$inferInsert; export type DailyHydration = typeof dailyHydration.$inferSelect; export type NewDailyHydration = typeof dailyHydration.$inferInsert; export type FitnessProfileHistory = typeof fitnessProfileHistory.$inferSelect; export type NewFitnessProfileHistory = typeof fitnessProfileHistory.$inferInsert; export type TrainerClientAssignment = typeof trainerClientAssignments.$inferSelect; export type NewTrainerClientAssignment = typeof trainerClientAssignments.$inferInsert;