hellth-hub/packages/db/src/schema/health.ts

637 lines
26 KiB
TypeScript
Raw Normal View History

import {
boolean,
date,
index,
integer,
jsonb,
numeric,
pgTable,
primaryKey,
text,
timestamp,
uniqueIndex,
} from "drizzle-orm/pg-core";
import { user } from "./auth";
import {
activityLevelEnum,
activitySourceEnum,
coachPriorityEnum,
dietStyleEnum,
foodSourceEnum,
healthGoalEnum,
mealTypeEnum,
portionUnitEnum,
reminderKindEnum,
wearableProviderEnum,
} from "./enums";
export const healthProfiles = pgTable("health_profiles", {
userId: text("userId").primaryKey().references(() => user.id, { onDelete: "cascade" }),
goal: healthGoalEnum("goal").notNull().default("healthier"),
age: integer("age"),
heightCm: numeric("heightCm", { precision: 6, scale: 2 }),
currentWeightKg: numeric("currentWeightKg", { precision: 6, scale: 2 }),
targetWeightKg: numeric("targetWeightKg", { precision: 6, scale: 2 }),
activityLevel: activityLevelEnum("activityLevel").notNull().default("moderate"),
dietStyle: dietStyleEnum("dietStyle").notNull().default("flexible"),
dietPreferences: jsonb("dietPreferences").$type<string[]>().notNull().default(["vegetarian", "vegan", "meat", "fish"]),
mealsPerDay: integer("mealsPerDay").notNull().default(3),
cooksPerDay: integer("cooksPerDay").notNull().default(1),
preferredMealPrepDays: integer("preferredMealPrepDays").notNull().default(0),
meatFishFrequency: text("meatFishFrequency").notNull().default("flexible"),
cookingTime: text("cookingTime").notNull().default("medium"),
budgetFocus: boolean("budgetFocus").notNull().default(false),
excludedFoods: text("excludedFoods"),
biggestChallenge: text("biggestChallenge"),
medicalNote: boolean("medicalNote").notNull().default(false),
completedAt: timestamp("completedAt"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export const healthGoals = pgTable("health_goals", {
userId: text("userId").primaryKey().references(() => user.id, { onDelete: "cascade" }),
drinkMl: integer("drinkMl").notNull().default(2500),
calories: integer("calories").notNull().default(2000),
carbsG: integer("carbsG").notNull().default(200),
fatG: integer("fatG").notNull().default(67),
proteinG: integer("proteinG").notNull().default(150),
weightKg: numeric("weightKg", { precision: 6, scale: 2 }),
fastingHours: integer("fastingHours").notNull().default(12),
weeklyCalories: integer("weeklyCalories"),
sportAddsCalories: boolean("sportAddsCalories").notNull().default(true),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export const foods = pgTable(
"foods",
{
id: text("id").primaryKey(),
ownerUserId: text("ownerUserId").references(() => user.id, { onDelete: "cascade" }),
name: text("name").notNull(),
brand: text("brand"),
barcode: text("barcode"),
category: text("category").notNull().default("Sonstiges"),
kcalPer100g: numeric("kcalPer100g", { precision: 8, scale: 2 }).notNull().default("0"),
proteinPer100g: numeric("proteinPer100g", { precision: 8, scale: 2 }).notNull().default("0"),
carbsPer100g: numeric("carbsPer100g", { precision: 8, scale: 2 }).notNull().default("0"),
fatPer100g: numeric("fatPer100g", { precision: 8, scale: 2 }).notNull().default("0"),
servingG: numeric("servingG", { precision: 8, scale: 2 }).notNull().default("100"),
preferredUnit: portionUnitEnum("preferredUnit").notNull().default("g"),
densityGPerMl: numeric("densityGPerMl", { precision: 8, scale: 4 }),
source: foodSourceEnum("source").notNull().default("manual"),
isVerified: boolean("isVerified").notNull().default(false),
isCommunity: boolean("isCommunity").notNull().default(false),
availableEverywhere: boolean("availableEverywhere").notNull().default(true),
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
ownerIdx: index("foods_owner_idx").on(table.ownerUserId),
barcodeIdx: index("foods_barcode_idx").on(table.barcode),
nameIdx: index("foods_name_idx").on(table.name),
}),
);
export const foodPortions = pgTable(
"food_portions",
{
id: text("id").primaryKey(),
foodId: text("foodId").notNull().references(() => foods.id, { onDelete: "cascade" }),
label: text("label").notNull(),
unit: portionUnitEnum("unit").notNull(),
amount: numeric("amount", { precision: 8, scale: 2 }).notNull(),
gramsEquivalent: numeric("gramsEquivalent", { precision: 8, scale: 2 }).notNull(),
createdAt: timestamp("createdAt").notNull().defaultNow(),
},
(table) => ({
foodIdx: index("food_portions_food_idx").on(table.foodId),
}),
);
export const foodNutritionSources = pgTable(
"food_nutrition_sources",
{
id: text("id").primaryKey(),
foodId: text("foodId").notNull().references(() => foods.id, { onDelete: "cascade" }),
provider: text("provider").notNull(),
providerFoodId: text("providerFoodId"),
name: text("name").notNull(),
brand: text("brand"),
url: text("url"),
kcalPer100g: numeric("kcalPer100g", { precision: 8, scale: 2 }).notNull().default("0"),
proteinPer100g: numeric("proteinPer100g", { precision: 8, scale: 2 }).notNull().default("0"),
carbsPer100g: numeric("carbsPer100g", { precision: 8, scale: 2 }).notNull().default("0"),
fatPer100g: numeric("fatPer100g", { precision: 8, scale: 2 }).notNull().default("0"),
servingG: numeric("servingG", { precision: 8, scale: 2 }),
confidence: integer("confidence").notNull().default(70),
raw: jsonb("raw").$type<Record<string, unknown>>().notNull().default({}),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
foodIdx: index("food_nutrition_sources_food_idx").on(table.foodId),
providerIdx: index("food_nutrition_sources_provider_idx").on(table.provider, table.providerFoodId),
}),
);
export const foodUserPreferences = pgTable(
"food_user_preferences",
{
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
foodId: text("foodId").notNull().references(() => foods.id, { onDelete: "cascade" }),
vote: integer("vote").notNull().default(0),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
pk: primaryKey({ columns: [table.userId, table.foodId] }),
userIdx: index("food_user_preferences_user_idx").on(table.userId),
foodIdx: index("food_user_preferences_food_idx").on(table.foodId),
}),
);
export const stores = pgTable("stores", {
id: text("id").primaryKey(),
ownerUserId: text("ownerUserId").references(() => user.id, { onDelete: "cascade" }),
name: text("name").notNull(),
address: text("address"),
placeId: text("placeId"),
latitude: numeric("latitude", { precision: 9, scale: 6 }),
longitude: numeric("longitude", { precision: 9, scale: 6 }),
favorite: boolean("favorite").notNull().default(false),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export const storeChangeRequests = pgTable(
"store_change_requests",
{
id: text("id").primaryKey(),
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
storeId: text("storeId").references(() => stores.id, { onDelete: "set null" }),
requestType: text("requestType").notNull().default("structure_update"),
status: text("status").notNull().default("pending"),
title: text("title").notNull(),
note: text("note"),
proposedStore: jsonb("proposedStore").$type<Record<string, unknown>>().notNull().default({}),
proposedDepartments: jsonb("proposedDepartments").$type<Array<Record<string, unknown>>>().notNull().default([]),
reviewedBy: text("reviewedBy").references(() => user.id, { onDelete: "set null" }),
reviewedAt: timestamp("reviewedAt"),
reviewNote: text("reviewNote"),
acceptedStoreId: text("acceptedStoreId").references(() => stores.id, { onDelete: "set null" }),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
userIdx: index("store_change_requests_user_idx").on(table.userId),
statusIdx: index("store_change_requests_status_idx").on(table.status, table.createdAt),
}),
);
export const storeUserPreferences = pgTable(
"store_user_preferences",
{
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
storeId: text("storeId").notNull().references(() => stores.id, { onDelete: "cascade" }),
favorite: boolean("favorite").notNull().default(false),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
pk: primaryKey({ columns: [table.userId, table.storeId] }),
userIdx: index("store_user_preferences_user_idx").on(table.userId),
}),
);
export const storeDepartments = pgTable(
"store_departments",
{
id: text("id").primaryKey(),
storeId: text("storeId").notNull().references(() => stores.id, { onDelete: "cascade" }),
name: text("name").notNull(),
sortOrder: integer("sortOrder").notNull().default(0),
},
(table) => ({
storeIdx: index("store_departments_store_idx").on(table.storeId),
}),
);
export const foodStores = pgTable(
"food_stores",
{
foodId: text("foodId").notNull().references(() => foods.id, { onDelete: "cascade" }),
storeId: text("storeId").notNull().references(() => stores.id, { onDelete: "cascade" }),
departmentId: text("departmentId").references(() => storeDepartments.id, { onDelete: "set null" }),
lastPrice: numeric("lastPrice", { precision: 8, scale: 2 }),
availabilityTags: jsonb("availabilityTags").$type<string[]>().notNull().default([]),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
pk: primaryKey({ columns: [table.foodId, table.storeId] }),
storeIdx: index("food_stores_store_idx").on(table.storeId),
}),
);
export const recipes = pgTable(
"recipes",
{
id: text("id").primaryKey(),
ownerUserId: text("ownerUserId").notNull().references(() => user.id, { onDelete: "cascade" }),
name: text("name").notNull(),
portions: integer("portions").notNull().default(1),
minutes: integer("minutes").notNull().default(0),
notes: text("notes"),
mealPrepDays: integer("mealPrepDays").notNull().default(0),
isPublic: boolean("isPublic").notNull().default(false),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
ownerIdx: index("recipes_owner_idx").on(table.ownerUserId),
}),
);
export const recipeIngredients = pgTable(
"recipe_ingredients",
{
id: text("id").primaryKey(),
recipeId: text("recipeId").notNull().references(() => recipes.id, { onDelete: "cascade" }),
foodId: text("foodId").notNull().references(() => foods.id, { onDelete: "restrict" }),
amountG: numeric("amountG", { precision: 8, scale: 2 }).notNull(),
displayAmount: numeric("displayAmount", { precision: 8, scale: 2 }),
displayUnit: portionUnitEnum("displayUnit"),
portionId: text("portionId").references(() => foodPortions.id, { onDelete: "set null" }),
sortOrder: integer("sortOrder").notNull().default(0),
},
(table) => ({
recipeIdx: index("recipe_ingredients_recipe_idx").on(table.recipeId),
}),
);
export const recipeSubmissions = pgTable(
"recipe_submissions",
{
id: text("id").primaryKey(),
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
name: text("name").notNull(),
portions: integer("portions").notNull().default(1),
minutes: integer("minutes").notNull().default(0),
notes: text("notes"),
mealPrepDays: integer("mealPrepDays").notNull().default(0),
status: text("status").notNull().default("pending"),
reviewNote: text("reviewNote"),
reviewedBy: text("reviewedBy").references(() => user.id, { onDelete: "set null" }),
reviewedAt: timestamp("reviewedAt"),
acceptedRecipeId: text("acceptedRecipeId").references(() => recipes.id, { onDelete: "set null" }),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
userIdx: index("recipe_submissions_user_idx").on(table.userId),
statusIdx: index("recipe_submissions_status_idx").on(table.status, table.createdAt),
}),
);
export const recipeSubmissionIngredients = pgTable(
"recipe_submission_ingredients",
{
id: text("id").primaryKey(),
submissionId: text("submissionId").notNull().references(() => recipeSubmissions.id, { onDelete: "cascade" }),
foodId: text("foodId").notNull().references(() => foods.id, { onDelete: "restrict" }),
amountG: numeric("amountG", { precision: 8, scale: 2 }).notNull(),
displayAmount: numeric("displayAmount", { precision: 8, scale: 2 }),
displayUnit: portionUnitEnum("displayUnit"),
portionId: text("portionId").references(() => foodPortions.id, { onDelete: "set null" }),
sortOrder: integer("sortOrder").notNull().default(0),
},
(table) => ({
submissionIdx: index("recipe_submission_ingredients_submission_idx").on(table.submissionId),
}),
);
export const recipeRatings = pgTable(
"recipe_ratings",
{
id: text("id").primaryKey(),
recipeId: text("recipeId").notNull().references(() => recipes.id, { onDelete: "cascade" }),
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
rating: integer("rating").notNull(),
comment: text("comment"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
uniqueUserRecipe: uniqueIndex("recipe_ratings_user_recipe_unique").on(table.recipeId, table.userId),
}),
);
export const mealPlans = pgTable(
"meal_plans",
{
id: text("id").primaryKey(),
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
date: date("date").notNull(),
mealType: mealTypeEnum("mealType").notNull(),
recipeId: text("recipeId").references(() => recipes.id, { onDelete: "set null" }),
name: text("name").notNull(),
portions: numeric("portions", { precision: 8, scale: 2 }).notNull().default("1"),
plannedCalories: integer("plannedCalories").notNull().default(0),
note: text("note"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
userDateIdx: index("meal_plans_user_date_idx").on(table.userId, table.date),
}),
);
export const mealLogs = pgTable(
"meal_logs",
{
id: text("id").primaryKey(),
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
date: date("date").notNull(),
mealType: mealTypeEnum("mealType").notNull(),
foodId: text("foodId").references(() => foods.id, { onDelete: "set null" }),
recipeId: text("recipeId").references(() => recipes.id, { onDelete: "set null" }),
name: text("name").notNull(),
amountLabel: text("amountLabel"),
amountG: numeric("amountG", { precision: 8, scale: 2 }),
calories: integer("calories").notNull().default(0),
proteinG: numeric("proteinG", { precision: 8, scale: 2 }).notNull().default("0"),
carbsG: numeric("carbsG", { precision: 8, scale: 2 }).notNull().default("0"),
fatG: numeric("fatG", { precision: 8, scale: 2 }).notNull().default("0"),
source: text("source").notNull().default("manual"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
userDateIdx: index("meal_logs_user_date_idx").on(table.userId, table.date),
}),
);
export const drinkLogs = pgTable(
"drink_logs",
{
id: text("id").primaryKey(),
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
date: date("date").notNull(),
amountMl: integer("amountMl").notNull(),
createdAt: timestamp("createdAt").notNull().defaultNow(),
},
(table) => ({
userDateIdx: index("drink_logs_user_date_idx").on(table.userId, table.date),
}),
);
export const weightLogs = pgTable(
"weight_logs",
{
id: text("id").primaryKey(),
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
date: date("date").notNull(),
weightKg: numeric("weightKg", { precision: 6, scale: 2 }).notNull(),
note: text("note"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
},
(table) => ({
uniqueUserDate: uniqueIndex("weight_logs_user_date_unique").on(table.userId, table.date),
}),
);
export const weeklyCaloriePlans = pgTable(
"weekly_calorie_plans",
{
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
date: date("date").notNull(),
plannedCalories: integer("plannedCalories").notNull(),
note: text("note"),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
pk: primaryKey({ columns: [table.userId, table.date] }),
}),
);
export const activityTemplates = pgTable(
"activity_templates",
{
id: text("id").primaryKey(),
ownerUserId: text("ownerUserId").references(() => user.id, { onDelete: "cascade" }),
name: text("name").notNull(),
category: text("category").notNull().default("habit"),
defaultMinutes: integer("defaultMinutes").notNull().default(10),
defaultPoints: integer("defaultPoints").notNull().default(10),
defaultCalories: integer("defaultCalories").notNull().default(0),
icon: text("icon").notNull().default("custom"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
},
(table) => ({
ownerIdx: index("activity_templates_owner_idx").on(table.ownerUserId),
}),
);
export const activityLogs = pgTable(
"activity_logs",
{
id: text("id").primaryKey(),
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
templateId: text("templateId").references(() => activityTemplates.id, { onDelete: "set null" }),
date: date("date").notNull(),
name: text("name").notNull(),
minutes: integer("minutes").notNull().default(0),
points: integer("points").notNull().default(0),
calories: integer("calories").notNull().default(0),
source: activitySourceEnum("source").notNull().default("manual"),
externalId: text("externalId"),
note: text("note"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
},
(table) => ({
userDateIdx: index("activity_logs_user_date_idx").on(table.userId, table.date),
externalUnique: uniqueIndex("activity_logs_source_external_unique").on(table.userId, table.source, table.externalId),
}),
);
export const shoppingLists = pgTable(
"shopping_lists",
{
id: text("id").primaryKey(),
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
storeId: text("storeId").references(() => stores.id, { onDelete: "set null" }),
name: text("name").notNull(),
startsOn: date("startsOn"),
endsOn: date("endsOn"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
userIdx: index("shopping_lists_user_idx").on(table.userId),
}),
);
export const shoppingListItems = pgTable(
"shopping_list_items",
{
id: text("id").primaryKey(),
shoppingListId: text("shoppingListId").notNull().references(() => shoppingLists.id, { onDelete: "cascade" }),
foodId: text("foodId").references(() => foods.id, { onDelete: "set null" }),
name: text("name").notNull(),
category: text("category").notNull().default("Sonstiges"),
amountG: numeric("amountG", { precision: 8, scale: 2 }).notNull().default("0"),
purchaseAmount: text("purchaseAmount"),
checked: boolean("checked").notNull().default(false),
sortOrder: integer("sortOrder").notNull().default(0),
},
(table) => ({
listIdx: index("shopping_list_items_list_idx").on(table.shoppingListId),
}),
);
export const wearableConnections = pgTable(
"wearable_connections",
{
id: text("id").primaryKey(),
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
provider: wearableProviderEnum("provider").notNull(),
providerUserId: text("providerUserId"),
accessTokenEncrypted: text("accessTokenEncrypted"),
refreshTokenEncrypted: text("refreshTokenEncrypted"),
scopes: jsonb("scopes").$type<string[]>().notNull().default([]),
expiresAt: timestamp("expiresAt"),
lastSyncAt: timestamp("lastSyncAt"),
syncEnabled: boolean("syncEnabled").notNull().default(true),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
uniqueProvider: uniqueIndex("wearable_connections_user_provider_unique").on(table.userId, table.provider),
}),
);
export const wearableImports = pgTable(
"wearable_imports",
{
id: text("id").primaryKey(),
connectionId: text("connectionId").notNull().references(() => wearableConnections.id, { onDelete: "cascade" }),
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
provider: wearableProviderEnum("provider").notNull(),
externalId: text("externalId").notNull(),
date: date("date").notNull(),
payload: jsonb("payload").$type<Record<string, unknown>>().notNull().default({}),
importedAt: timestamp("importedAt").notNull().defaultNow(),
},
(table) => ({
uniqueExternal: uniqueIndex("wearable_imports_provider_external_unique").on(table.provider, table.externalId),
userDateIdx: index("wearable_imports_user_date_idx").on(table.userId, table.date),
}),
);
export const coachPrinciples = pgTable("coach_principles", {
id: text("id").primaryKey(),
title: text("title").notNull(),
body: text("body").notNull(),
category: text("category").notNull().default("general"),
isActive: boolean("isActive").notNull().default(true),
sortOrder: integer("sortOrder").notNull().default(0),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export const coachRecommendations = pgTable(
"coach_recommendations",
{
id: text("id").primaryKey(),
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
title: text("title").notNull(),
body: text("body").notNull(),
priority: coachPriorityEnum("priority").notNull().default("medium"),
category: text("category").notNull().default("daily"),
source: text("source").notNull().default("rule"),
validFrom: date("validFrom"),
validTo: date("validTo"),
dismissedAt: timestamp("dismissedAt"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
},
(table) => ({
userIdx: index("coach_recommendations_user_idx").on(table.userId),
}),
);
export const reminderRules = pgTable(
"reminder_rules",
{
id: text("id").primaryKey(),
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
kind: reminderKindEnum("kind").notNull(),
enabled: boolean("enabled").notNull().default(true),
intervalMinutes: integer("intervalMinutes").notNull().default(90),
startTime: text("startTime").notNull().default("08:00"),
endTime: text("endTime").notNull().default("20:00"),
message: text("message").notNull(),
quietDays: jsonb("quietDays").$type<string[]>().notNull().default([]),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
uniqueKind: uniqueIndex("reminder_rules_user_kind_unique").on(table.userId, table.kind),
}),
);
export const clubActivities = pgTable(
"club_activities",
{
id: text("id").primaryKey(),
hostUserId: text("hostUserId").notNull().references(() => user.id, { onDelete: "cascade" }),
title: text("title").notNull(),
category: text("category").notNull().default("other"),
scope: text("scope").notNull().default("nationwide"),
state: text("state"),
city: text("city"),
latitude: numeric("latitude", { precision: 9, scale: 6 }),
longitude: numeric("longitude", { precision: 9, scale: 6 }),
locationLabel: text("locationLabel"),
startsAt: timestamp("startsAt").notNull(),
durationMinutes: integer("durationMinutes").notNull().default(30),
points: integer("points").notNull().default(20),
note: text("note"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
},
(table) => ({
startsAtIdx: index("club_activities_starts_at_idx").on(table.startsAt),
}),
);
export const clubProfiles = pgTable("club_profiles", {
userId: text("userId").primaryKey().references(() => user.id, { onDelete: "cascade" }),
state: text("state").notNull().default("Bayern"),
city: text("city"),
latitude: numeric("latitude", { precision: 9, scale: 6 }),
longitude: numeric("longitude", { precision: 9, scale: 6 }),
privacyMode: text("privacyMode").notNull().default("state"),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export const clubActivityParticipants = pgTable(
"club_activity_participants",
{
activityId: text("activityId").notNull().references(() => clubActivities.id, { onDelete: "cascade" }),
userId: text("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
displayName: text("displayName").notNull(),
joinedAt: timestamp("joinedAt").notNull().defaultNow(),
completedAt: timestamp("completedAt"),
},
(table) => ({
pk: primaryKey({ columns: [table.activityId, table.userId] }),
}),
);
export type HealthProfile = typeof healthProfiles.$inferSelect;
export type NewHealthProfile = typeof healthProfiles.$inferInsert;
export type HealthGoal = typeof healthGoals.$inferSelect;
export type NewHealthGoal = typeof healthGoals.$inferInsert;
export type Food = typeof foods.$inferSelect;
export type NewFood = typeof foods.$inferInsert;
export type Recipe = typeof recipes.$inferSelect;
export type NewRecipe = typeof recipes.$inferInsert;