diff --git a/apps/next/src/app/nutrition/page.tsx b/apps/next/src/app/nutrition/page.tsx index 86d8e257..87675cdf 100644 --- a/apps/next/src/app/nutrition/page.tsx +++ b/apps/next/src/app/nutrition/page.tsx @@ -2,19 +2,29 @@ import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; +import SearchMealCard from "@/components/ui/card/search-meal-card"; +import TrackedMealCard from "@/components/ui/card/tracked-meal-card"; +import MealSearchDialog from "@/components/ui/meal-search"; +import MobileCalorieCard from "@/components/ui/mobile-calorie-card"; +import MobileNutritionBars from "@/components/ui/mobile-nutrition-bars"; import NutritionBreakdown from "@/components/ui/nutrition-breakdown"; +import NutritionGoals from "@/components/ui/nutrition-goals"; +import TrackerHistory from "@/components/ui/tracker-history"; +import TrackerHistoryDialog from "@/components/ui/tracker-history-dialog"; +import TrackerHistoryDrawer from "@/components/ui/tracker-history-drawer"; import { useSnackbarStore } from "@/context/useSnackbar"; import { useUserStore } from "@/context/useUserStore"; +import { useMediaQuery } from "@/hooks/useMediaQuery"; import { trpc } from "@/utils/trpc"; export default function MealTracker() { + const utils = trpc.useUtils(); const router = useRouter(); const { userId, isInitialized } = useUserStore(); const { showSnackbar } = useSnackbarStore(); useEffect(() => { if (!isInitialized) return; - if (!userId) { showSnackbar("Login to track meals!", "error"); router.push("/"); @@ -29,7 +39,30 @@ export default function MealTracker() { { userId: userId ?? "" }, { enabled: !!userId }, ); - const [activeDayIndex, setActiveDayIndex] = useState(null); + + const today = new Date().toISOString().split("T")[0]; + + const { data: defaultGoals } = trpc.nutrition.getGoals.useQuery({ + userId: userId ?? "", + }); + const { data: dayGoals } = trpc.nutrition.getGoalsByDay.useQuery( + { userId: userId ?? "", date: today }, + { enabled: !!userId }, + ); + const goals = dayGoals ?? defaultGoals; + + const [historyDialogOpen, setHistoryDialogOpen] = useState(false); + const [historyDate, setHistoryDate] = useState(null); + + const historyDateStr = historyDate + ? historyDate.toISOString().split("T")[0] + : today; + + const { data: historyDayGoals } = trpc.nutrition.getGoalsByDay.useQuery( + { userId: userId ?? "", date: historyDateStr }, + { enabled: !!userId && !!historyDate }, + ); + const historyGoals = historyDayGoals ?? defaultGoals; const mealsGroupedByDay = useMemo(() => { if (!meals) return []; @@ -50,15 +83,7 @@ export default function MealTracker() { return result.sort((a, b) => b.rawDate.getTime() - a.rawDate.getTime()); }, [meals]); - useEffect(() => { - if (mealsGroupedByDay.length > 0 && activeDayIndex === null) { - setActiveDayIndex(0); - } - }, [mealsGroupedByDay, activeDayIndex]); - - if (isLoading) return
Loading meals...
; - if (error) return
Error loading meals
; - + const activeDayIndex = mealsGroupedByDay.length > 0 ? 0 : null; const selectedDay = activeDayIndex !== null ? mealsGroupedByDay[activeDayIndex] : null; @@ -67,36 +92,346 @@ export default function MealTracker() { return Number.isFinite(n) ? n : 0; }; + const loggedDates = useMemo(() => { + if (!meals) return []; + return [ + ...new Set(meals.map((m) => new Date(m.eatenAt).toDateString())), + ].map((d) => new Date(d)); + }, [meals]); + + // Checks dish availability + const { data: hallData } = trpc.peterplate.useQuery( + { date: selectedDay?.rawDate ?? new Date() }, + { enabled: Boolean(selectedDay) }, + ); + + const availableDishIds = useMemo(() => { + const set = new Set(); + const halls = [hallData?.anteatery, hallData?.brandywine].filter(Boolean); + for (const hall of halls) { + for (const menu of hall?.menus ?? []) { + for (const station of menu.stations ?? []) { + for (const dish of station.dishes ?? []) { + set.add(dish.id); + } + } + } + } + return set; + }, [hallData]); + + const isUnavailable = (dishId: string) => + Boolean(hallData) && !availableDishIds.has(dishId); + + // Flatten today's dishes for search + const availableDishes = useMemo(() => { + const dishes: { + id: string; + name: string; + calories: number; + protein: number; + carbs: number; + fat: number; + image_url?: string | null; + }[] = []; + + const halls = [hallData?.anteatery, hallData?.brandywine].filter(Boolean); + const seen = new Set(); + + for (const hall of halls) { + for (const menu of hall?.menus ?? []) { + for (const station of menu.stations ?? []) { + for (const dish of station.dishes ?? []) { + if (seen.has(dish.id)) continue; + seen.add(dish.id); + dishes.push({ + id: dish.id, + name: dish.name, + calories: Number(dish.nutritionInfo?.calories ?? 0), + protein: Number(dish.nutritionInfo?.proteinG ?? 0), + carbs: Number(dish.nutritionInfo?.totalCarbsG ?? 0), + fat: Number(dish.nutritionInfo?.totalFatG ?? 0), + image_url: dish.image_url, + }); + } + } + } + } + + return dishes; + }, [hallData]); + + // suggested meals memo + const suggestedMeals = useMemo(() => { + if (!meals) return []; + const servingCounts: Record = {}; + const latestByDish: Record = {}; + + for (const meal of meals) { + if (!meal.dishId) continue; + servingCounts[meal.dishId] = + (servingCounts[meal.dishId] ?? 0) + (meal.servings ?? 1); + if (!latestByDish[meal.dishId]) latestByDish[meal.dishId] = meal; + } + + return Object.entries(servingCounts) + .filter(([, total]) => total >= 1) + .sort(([, a], [, b]) => b - a) + .map(([dishId]) => { + const meal = latestByDish[dishId]!; + return { + ...meal, + calories: Number(meal.calories ?? 0), + protein: Number(meal.protein ?? 0), + carbs: Number(meal.carbs ?? 0), + fat: Number(meal.fat ?? 0), + servings: 1, + }; + }); + }, [meals]); + + // logmeal mutation + const logMeal = trpc.nutrition.logMeal.useMutation({ + onSuccess: async () => { + await utils.nutrition.invalidate(); + }, + onError: (err) => { + console.error(err.message); + }, + }); + + const visibleMeals = selectedDay?.items ?? []; + const countedMeals = visibleMeals.filter( + (m) => (m.servings ?? 0) > 0 && !isUnavailable(m.dishId), + ); + + const isMobile = useMediaQuery("(max-width: 768px)"); + + if (isLoading) return
Loading meals...
; + if (error) return
Error loading meals
; + return ( -
-
- {mealsGroupedByDay.map((day, index) => ( - - ))} - {mealsGroupedByDay.length === 0 &&
No meals logged recently.
} -
+
+
+

+ + Tracker + {selectedDay && ( + + -{" "} + {selectedDay.rawDate.toLocaleDateString("en-US", { + month: "numeric", + day: "numeric", + year: "2-digit", + })} + + )} + + {/* History button - mobile only */} +
+ {userId && ( + {}} + onDayClick={(date) => { + setHistoryDate(date); + setHistoryDialogOpen(true); + }} + loggedDates={loggedDates} + /> + )} +
+

+ + {/* Subheading + History - desktop only */} +
+

+ Keep track of your health using our Nutrition Tracker! Add dishes to + count them towards your totals! +

+ {userId && ( + {}} + onDayClick={(date) => { + setHistoryDate(date); + setHistoryDialogOpen(true); + }} + loggedDates={loggedDates} + /> + )} +
-
- {selectedDay && ( - ({ + {/* Desktop: NutritionBreakdown */} +
+ {mealsGroupedByDay.length === 0 ? ( +
No meals logged recently.
+ ) : ( + ({ + ...m, + calories: toNum(m.calories), + protein: toNum(m.protein), + carbs: toNum(m.carbs), + fat: toNum(m.fat), + }))} + calorieGoal={goals?.calorieGoal ?? 2000} + proteinGoal={goals?.proteinGoal ?? 75} + carbGoal={goals?.carbGoal ?? 250} + fatGoal={goals?.fatGoal ?? 50} + /> + )} +
+ {userId && ( + + )} +
+
+ + {/* Mobile: MobileCalorieCard + MobileNutritionBars */} +
+ ({ + ...m, + calories: toNum(m.calories), + protein: toNum(m.protein), + carbs: toNum(m.carbs), + fat: toNum(m.fat), + }))} + calorieGoal={goals?.calorieGoal ?? 2000} + userId={userId ?? ""} + date={new Date().toISOString().split("T")[0]} + /> + ({ ...m, calories: toNum(m.calories), protein: toNum(m.protein), carbs: toNum(m.carbs), fat: toNum(m.fat), }))} + proteinGoal={goals?.proteinGoal ?? 75} + carbGoal={goals?.carbGoal ?? 250} + fatGoal={goals?.fatGoal ?? 50} + /> +
+ + {/* History dialog/drawer */} + {isMobile ? ( + setHistoryDialogOpen(false)} + selectedDate={historyDate} + allMeals={ + meals?.map((m) => ({ + ...m, + calories: toNum(m.calories), + protein: toNum(m.protein), + carbs: toNum(m.carbs), + fat: toNum(m.fat), + })) ?? [] + } + calorieGoal={historyGoals?.calorieGoal ?? 2000} + proteinGoal={historyGoals?.proteinGoal ?? 100} + carbGoal={historyGoals?.carbGoal ?? 250} + fatGoal={historyGoals?.fatGoal ?? 50} + userId={userId ?? ""} + /> + ) : ( + setHistoryDialogOpen(false)} + selectedDate={historyDate} + allMeals={ + meals?.map((m) => ({ + ...m, + calories: toNum(m.calories), + protein: toNum(m.protein), + carbs: toNum(m.carbs), + fat: toNum(m.fat), + })) ?? [] + } + calorieGoal={historyGoals?.calorieGoal ?? 2000} + proteinGoal={historyGoals?.proteinGoal ?? 100} + carbGoal={historyGoals?.carbGoal ?? 250} + fatGoal={historyGoals?.fatGoal ?? 50} + userId={userId ?? ""} /> )} + + {/* Counted Foods */} +
+

+ Counted Foods +

+ {countedMeals.length === 0 ? ( +

No counted foods.

+ ) : ( +
+ {countedMeals.map((meal) => ( + + ))} +
+ )} +
+ + {/* Suggested Foods */} +
+

+ Suggested Foods +

+
+ {suggestedMeals.length === 0 ? ( +

+ Dishes from the past week logged 5+ times will appear here. +

+ ) : ( + suggestedMeals.map((meal) => ( + + logMeal.mutate({ + userId: userId ?? "", + dishId: meal.dishId ?? "", + dishName: meal.dishName ?? "", + servings, + eatenAt: new Date(), + }) + } + /> + )) + )} +
+
+ + {/* Floating search button + dialog/drawer */} + + logMeal.mutate({ + userId: userId ?? "", + dishId, + dishName, + servings, + eatenAt: new Date(), + }) + } + />
); } diff --git a/apps/next/src/components/progress-donut.tsx b/apps/next/src/components/progress-donut.tsx index 990b1167..819ff64f 100644 --- a/apps/next/src/components/progress-donut.tsx +++ b/apps/next/src/components/progress-donut.tsx @@ -18,41 +18,57 @@ interface Props { * The unit of measurement to display within the progress donut. Ex: 'g' for grams */ display_unit: string; + + /** + * ring colors, passing props to add custom colors to certain goals + */ + trackColor?: string; + progressColor?: string; } export function ProgressDonut({ progress_value, max_value, display_unit, + trackColor = "#ffffff", + progressColor = "#0084D1", }: Props) { const value = Math.max(0, Math.min(progress_value, max_value)); const percent = value / max_value; const strokeDashoffset = CIRCLE_CIRCUMFERENCE * (1 - percent); return ( -
-
+
+
Progress Donut + {/* outer translucent white circle */} + + {/* inner white circle */} + + {/* background arc track - semicircle */} + {/* progress arc */} @@ -61,7 +77,7 @@ export function ProgressDonut({ {progress_value} {display_unit} - / {max_value} + / {max_value}
diff --git a/apps/next/src/components/ui/card/search-meal-card.tsx b/apps/next/src/components/ui/card/search-meal-card.tsx new file mode 100644 index 00000000..665ab883 --- /dev/null +++ b/apps/next/src/components/ui/card/search-meal-card.tsx @@ -0,0 +1,191 @@ +"use client"; + +import { + Add, + ArrowDropDown, + ArrowDropUp, + Restaurant, +} from "@mui/icons-material"; +import { Card, CardContent } from "@mui/material"; +import type { SelectLoggedMeal } from "@peterplate/db"; +import Image from "next/image"; +import React from "react"; +import { getFoodIcon } from "@/utils/funcs"; +import { trpc } from "@/utils/trpc"; +import { cn } from "@/utils/tw"; + +type LoggedMealJoinedWithNutrition = SelectLoggedMeal & { + calories: number; + protein: number; + carbs: number; + fat: number; +}; + +interface Props { + meal: LoggedMealJoinedWithNutrition; + isUnavailable?: boolean; + /** Called when the user clicks "+". Receives the meal*/ + onAdd?: (meal: LoggedMealJoinedWithNutrition, servings: number) => void; +} + +export default function SearchMealCard({ meal, isUnavailable, onAdd }: Props) { + const [servingsDraft, setServingsDraft] = React.useState(meal.servings ?? 1); + const [imageError, setImageError] = React.useState(false); + + // fetch dish detail for image URL + icon + const { data } = trpc.peterplate.useQuery( + { date: new Date(meal.eatenAt) }, + { enabled: !!meal.dishId }, + ); + + const dish = React.useMemo(() => { + const halls = [data?.anteatery, data?.brandywine].filter(Boolean); + for (const hall of halls) { + for (const menu of hall?.menus ?? []) { + for (const station of menu.stations ?? []) { + const found = station.dishes?.find((d) => d.id === meal.dishId); + if (found) return found; + } + } + } + return undefined; + }, [data, meal.dishId]); + + const imageUrl = dish?.image_url; + const dishNameForIcon = dish?.name ?? meal.dishName; + const showImage = + typeof imageUrl === "string" && imageUrl.trim() !== "" && !imageError; + const IconComponent = getFoodIcon(dishNameForIcon ?? "") ?? Restaurant; + + return ( +
+ + +
+
+
+ {showImage && imageUrl ? ( + setImageError(true)} + /> + ) : ( + + )} + +
+

+ {meal.dishName} +

+
+
+
+ {servingsDraft} +
+ +
+ + + +
+
+ + + serving{servingsDraft !== 1 ? "s" : ""}/bowl + {servingsDraft !== 1 ? "s" : ""} + +
+
+
+ + {/* Nutrition content */} +
+ {Math.round(meal.calories * servingsDraft)} cal + {Math.round(meal.protein * servingsDraft)}g protein + {Math.round(meal.carbs * servingsDraft)}g carbs + {Math.round(meal.fat * servingsDraft)}g fat +
+
+ + {/* "+"" button instead of trash one */} +
+ +
+
+
+
+
+ ); +} diff --git a/apps/next/src/components/ui/card/tracked-meal-card.tsx b/apps/next/src/components/ui/card/tracked-meal-card.tsx new file mode 100644 index 00000000..b0d3fa31 --- /dev/null +++ b/apps/next/src/components/ui/card/tracked-meal-card.tsx @@ -0,0 +1,369 @@ +"use client"; + +import { + ArrowDropDown, + ArrowDropUp, + Delete, + Restaurant, +} from "@mui/icons-material"; +import { Card, CardContent, Dialog, Drawer } from "@mui/material"; +import type { SelectLoggedMeal } from "@peterplate/db"; +import Image from "next/image"; +import React from "react"; +import FoodDialogContent from "@/components/ui/food-dialog-content"; +import FoodDrawerContent from "@/components/ui/food-drawer-content"; +import { useDate } from "@/context/date-context"; +import { useHallStore } from "@/context/useHallStore"; +import { useMediaQuery } from "@/hooks/useMediaQuery"; +import { getFoodIcon } from "@/utils/funcs"; +import { trpc } from "@/utils/trpc"; +import { cn } from "@/utils/tw"; + +type LoggedMealJoinedWithNutrition = SelectLoggedMeal & { + calories: number; + protein: number; + carbs: number; + fat: number; +}; + +interface Props { + meal: LoggedMealJoinedWithNutrition; + isUnavailable?: boolean; +} + +interface TrackedMealCardContentProps + extends React.HTMLAttributes { + meal: LoggedMealJoinedWithNutrition; + dishNameForIcon?: string; + imageUrl?: string; + isUnavailable?: boolean; + onDelete?: () => void; + deleteDisabled?: boolean; + servingsDraft: number; + onChangeServings: (next: number) => void; + servingsDisabled?: boolean; +} + +const TrackedMealCardContent = React.forwardRef< + HTMLDivElement, + TrackedMealCardContentProps +>( + ( + { + meal, + dishNameForIcon, + imageUrl, + isUnavailable = false, + onDelete, + deleteDisabled, + servingsDraft, + onChangeServings, + servingsDisabled, + className, + ...divProps + }, + ref, + ) => { + const [imageError, setImageError] = React.useState(false); + const showImage = + typeof imageUrl === "string" && imageUrl.trim() !== "" && !imageError; + + const IconComponent = + (dishNameForIcon ? getFoodIcon(dishNameForIcon) : undefined) ?? + Restaurant; + + return ( +
+ + +
+
+
+ {showImage && imageUrl ? ( + setImageError(true)} + /> + ) : ( + + )} + +
+

+ {meal.dishName} +

+ + {/* Edit Servings/Bowls */} +
+
+
+ {servingsDraft} +
+ +
+ + + +
+
+ + + serving{servingsDraft !== 1 ? "s" : ""}/bowl + {servingsDraft !== 1 ? "s" : ""} + +
+
+
+ +
+ {Math.round(meal.calories * servingsDraft)} cal + + {Math.round(meal.protein * servingsDraft)}g protein + + {Math.round(meal.carbs * servingsDraft)}g carbs + {Math.round(meal.fat * servingsDraft)}g fat +
+
+
+ {/* Delete button */} + +
+
+
+
+
+ ); + }, +); +TrackedMealCardContent.displayName = "TrackedMealCardContent"; + +export default function TrackedMealCard({ + meal, + isUnavailable = false, +}: Props) { + /* Handle Display Food Card Info */ + const isDesktop = useMediaQuery("(min-width: 768px)"); + const [open, setOpen] = React.useState(false); + const handleOpen = () => setOpen(true); + const handleClose = () => setOpen(false); + + const { selectedDate } = useDate(); + const today = useHallStore((s) => s.today); + + const { data, isLoading } = trpc.peterplate.useQuery( + { date: selectedDate ?? today }, + { enabled: open }, + ); + + const dish = React.useMemo(() => { + const halls = [data?.anteatery, data?.brandywine].filter(Boolean); + for (const hall of halls) { + for (const menu of hall?.menus ?? []) { + for (const station of menu.stations ?? []) { + const found = station.dishes?.find((d) => d.id === meal.dishId); + if (found) return found; + } + } + } + return undefined; + }, [data, meal.dishId]); + + const utils = trpc.useUtils(); + + /* Handle Edit Servings/Bowls */ + const [servingsDraft, setServingsDraft] = React.useState(meal.servings); + React.useEffect(() => { + setServingsDraft(meal.servings); + }, [meal.servings]); + + const updateServings = trpc.nutrition.updateLoggedMeal.useMutation({ + onSuccess: async () => { + await utils.nutrition.invalidate(); + }, + onError: (err) => { + console.error(err.message); + setServingsDraft(meal.servings); + }, + }); + + const handleChangeServings = (next: number) => { + setServingsDraft(next); + updateServings.mutate({ id: meal.id, servings: next }); + }; + + /* Handle Delete Button */ + const deleteLoggedMeal = trpc.nutrition.deleteLoggedMeal.useMutation({ + onSuccess: async () => { + await utils.nutrition.invalidate(); + }, + onError: (err) => { + console.error(err.message); + }, + }); + + const handleDelete = () => { + deleteLoggedMeal.mutate({ + id: meal.id, + }); + }; + + const imageUrl = dish?.image_url; + const dishNameForIcon = dish?.name ?? meal.dishName; + + if (isDesktop) + return ( + <> + + + + {dish ? ( + + ) : ( +
+ {isLoading ? "Loading..." : "Dish not found"} +
+ )} +
+ + ); + + return ( + <> + + + + {dish ? ( + + ) : ( +
+ {isLoading ? "Loading..." : "Dish not found"} +
+ )} +
+ + ); +} diff --git a/apps/next/src/components/ui/meal-search.tsx b/apps/next/src/components/ui/meal-search.tsx new file mode 100644 index 00000000..bd317a4a --- /dev/null +++ b/apps/next/src/components/ui/meal-search.tsx @@ -0,0 +1,359 @@ +"use client"; + +import { Add, Close, Search } from "@mui/icons-material"; +import { + Dialog, + DialogContent, + DialogTitle, + Drawer, + Fab, + IconButton, + InputAdornment, + TextField, +} from "@mui/material"; +import { useMemo, useState } from "react"; +import SearchMealCard from "@/components/ui/card/search-meal-card"; +import { useMediaQuery } from "@/hooks/useMediaQuery"; + +// had to add tailwind colors in here to refernece for mui sx props +const colors = { + sky100: "#e0f2fe", // sky-100 + sky200: "#bae6fd", // sky-200 + sky500: "#0ea5e9", // sky-500 + sky600: "#0284c7", // sky-600 + sky700: "#0369a1", // sky-700 + sky800: "#075985", // sky-800 + slate500: "#64748b", // slate-500 +} as const; + +export interface AvailableDish { + id: string; + name: string; + calories: number; + protein: number; + carbs: number; + fat: number; + image_url?: string | null; +} + +export interface SuggestedMeal { + id: string; + dishId: string; + dishName: string; + servings: number; + calories: number; + protein: number; + carbs: number; + fat: number; + userId: string; + eatenAt: Date; +} + +interface MealSearchDialogProps { + availableDishes: AvailableDish[]; + suggestedMeals: SuggestedMeal[]; + onAdd: (dishId: string, dishName: string, servings: number) => void; +} + +//card list, searches through availableDishes (only dishes today at anteatery/brandwine) + +function MealList({ + query, + availableDishes, + suggestedMeals, + onAdd, +}: { + query: string; + availableDishes: AvailableDish[]; + suggestedMeals: SuggestedMeal[]; + onAdd: (dishId: string, dishName: string, servings: number) => void; +}) { + const showSearch = query.trim().length > 0; + + const searchResults = useMemo(() => { + if (!showSearch) return []; + const lower = query.toLowerCase(); + return availableDishes.filter((d) => d.name.toLowerCase().includes(lower)); + }, [query, availableDishes, showSearch]); + + if (showSearch) { + return ( +
+
+ + Search Results... + +
+
+ + {searchResults.length === 0 ? ( +

+ No dishes found matching "{query}". +

+ ) : ( + searchResults.map((dish) => ( +
+ + onAdd(meal.dishId ?? "", meal.dishName ?? "", servings) + } + /> +
+ )) + )} +
+ ); + } + + return ( +
+
+
+
+ + {suggestedMeals.length === 0 ? ( +

+ Log a dish for it to appear here! +

+ ) : ( + suggestedMeals.map((meal) => ( +
+ + onAdd(meal.dishId ?? "", meal.dishName ?? "", servings) + } + /> +
+ )) + )} +
+ ); +} + +function SearchBar({ + query, + onChange, +}: { + query: string; + onChange: (v: string) => void; +}) { + return ( + onChange(e.target.value)} + size="small" + InputProps={{ + startAdornment: ( + + + + ), + sx: { + backgroundColor: "white", + borderRadius: "12px", + "& fieldset": { borderColor: colors.sky200 }, + "&:hover fieldset": { borderColor: colors.sky500 }, + "&.Mui-focused fieldset": { borderColor: colors.sky600 }, + }, + }} + /> + ); +} + +// desktop dialog: +// TODO: for some reason, there is a lot of white space on the desktop component despite trying to alter the padding and sx width props +function DesktopMealSearchDialog({ + availableDishes, + suggestedMeals, + onAdd, + open, + onClose, +}: MealSearchDialogProps & { open: boolean; onClose: () => void }) { + const [query, setQuery] = useState(""); + + return ( + + {/* sky-100 header — X inline with search bar, no extra top space */} + +
+
+ +
+ + + +
+
+ + {/* Scrollable list — no bottom close button on desktop */} + + + +
+ ); +} + +// mobile drawer + +function MobileMealSearchDrawer({ + availableDishes, + suggestedMeals, + onAdd, + open, + onClose, +}: MealSearchDialogProps & { open: boolean; onClose: () => void }) { + const [query, setQuery] = useState(""); + + return ( + +
+
+
+ +
+ + + +
+
+ + {/* Scrollable list */} +
+ +
+ +
+ +
+
+ ); +} + +// plus button that renders dialog/drawer + +export default function MealSearchDialog(props: MealSearchDialogProps) { + const [open, setOpen] = useState(false); + const isDesktop = useMediaQuery("(min-width: 768px)"); + + return ( + <> + setOpen(true)} + sx={{ + position: "fixed", + bottom: 80, + right: 32, + backgroundColor: colors.sky700, + "&:hover": { backgroundColor: colors.sky800 }, + color: "white", + zIndex: 50, + }} + > + + + + {isDesktop ? ( + setOpen(false)} + /> + ) : ( + setOpen(false)} + /> + )} + + ); +} diff --git a/apps/next/src/components/ui/mobile-calorie-card.tsx b/apps/next/src/components/ui/mobile-calorie-card.tsx new file mode 100644 index 00000000..915bc56f --- /dev/null +++ b/apps/next/src/components/ui/mobile-calorie-card.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { ProgressDonut } from "../progress-donut"; +import { + compileMealData, + type LoggedMealJoinedWithNutrition, +} from "./nutrition-breakdown"; +import NutritionGoals from "./nutrition-goals"; + +interface Props { + mealsEaten: LoggedMealJoinedWithNutrition[]; + calorieGoal: number; + userId: string; + hideEditButton?: boolean; + date?: string; +} + +export default function MobileCalorieCard({ + mealsEaten, + calorieGoal, + userId, + hideEditButton = false, + date, +}: Props) { + const nutrition = compileMealData(mealsEaten); + + return ( +
+
+ {!hideEditButton && } +
+ Calories +
+ +
+
+ ); +} diff --git a/apps/next/src/components/ui/mobile-nutrition-bars.tsx b/apps/next/src/components/ui/mobile-nutrition-bars.tsx new file mode 100644 index 00000000..d0573e44 --- /dev/null +++ b/apps/next/src/components/ui/mobile-nutrition-bars.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { + compileMealData, + type LoggedMealJoinedWithNutrition, +} from "@/components/ui/nutrition-breakdown"; + +interface MacroBarProps { + label: string; + value: number; + max: number; + unit: string; +} + +function MacroBar({ label, value, max, unit }: MacroBarProps) { + const percent = Math.min((value / max) * 100, 100); + return ( +
+ {label} +
+
+
+ + {Math.round(value)} + {unit} / {max} + {unit} + +
+ ); +} + +interface Props { + mealsEaten: LoggedMealJoinedWithNutrition[]; + proteinGoal: number; + carbGoal: number; + fatGoal: number; +} + +export default function MobileNutritionBars({ + mealsEaten, + proteinGoal, + carbGoal, + fatGoal, +}: Props) { + const nutrition = compileMealData(mealsEaten); + + return ( +
+ + + +
+ ); +} diff --git a/apps/next/src/components/ui/nutrition-breakdown.tsx b/apps/next/src/components/ui/nutrition-breakdown.tsx index 7e7c8b47..17ad2a2e 100644 --- a/apps/next/src/components/ui/nutrition-breakdown.tsx +++ b/apps/next/src/components/ui/nutrition-breakdown.tsx @@ -1,12 +1,7 @@ -import { - DeleteOutline, - KeyboardArrowDown, - KeyboardArrowUp, -} from "@mui/icons-material"; -import { IconButton, Stack } from "@mui/material"; +"use client"; + import type React from "react"; -import { useSnackbarStore } from "@/context/useSnackbar"; -import { formatFoodName } from "@/utils/funcs"; +import { useMediaQuery } from "@/hooks/useMediaQuery"; import { trpc } from "@/utils/trpc"; import type { SelectLoggedMeal } from "../../../../../packages/db/src/schema"; import { ProgressDonut } from "../progress-donut"; @@ -28,12 +23,7 @@ type LoggedMealJoinedWithNutrition = SelectLoggedMeal & { function compileMealData( meals: LoggedMealJoinedWithNutrition[], ): NutritionData { - const data = { - calories: 0, - protein_g: 0, - carbs_g: 0, - fat_g: 0, - }; + const data = { calories: 0, protein_g: 0, carbs_g: 0, fat_g: 0 }; for (const meal of meals) { const servings = meal.servings; @@ -52,191 +42,101 @@ function compileMealData( } interface Props { - dateString: string; mealsEaten: LoggedMealJoinedWithNutrition[]; - userId: string; + calorieGoal: number; + proteinGoal: number; + carbGoal: number; + fatGoal: number; } -const NutritionBreakdown = ({ dateString, mealsEaten }: Props) => { - const { showSnackbar } = useSnackbarStore(); - const nutrition: NutritionData = compileMealData(mealsEaten); +const NutritionBreakdown = ({ + mealsEaten, + calorieGoal, + proteinGoal, + carbGoal, + fatGoal, +}: Props) => { + const nutrition = compileMealData(mealsEaten); - const utils = trpc.useUtils(); + const isLg = useMediaQuery("(min-width: 1024px)"); + const isXl = useMediaQuery("(min-width: 1280px)"); - const updateMealMutation = trpc.nutrition.updateLoggedMeal.useMutation({ - onSuccess: (data) => { - showSnackbar( - `Updated ${formatFoodName(data.dishName)} quantity`, - "success", - ); + const utils = trpc.useUtils(); + const deleteLoggedMealMutation = trpc.nutrition.deleteLoggedMeal.useMutation({ + onSuccess: () => { + alert(`Removed dish from your log`); utils.nutrition.invalidate(); }, onError: (error) => { - console.error(error); - showSnackbar("Failed to update quantity", "error"); + console.error(error.message); }, }); - const deleteMealMutation = trpc.nutrition.deleteLoggedMeal.useMutation({ - onSuccess: (data) => { - showSnackbar( - `Removed ${formatFoodName(data.dishName)} from your log`, - "success", - ); - utils.nutrition.invalidate(); - }, - onError: (error) => { - console.error(error); - showSnackbar("Failed to remove meal from your log", "error"); - }, - }); + const cols = isXl ? 4 : isLg ? 2 : 1; - const handleAdjustQuantity = ( - meal: LoggedMealJoinedWithNutrition, - newServings: number, - ) => { - if (newServings < 0.5) { - showSnackbar("Minimum serving size is 0.5", "error"); - return; - } - - updateMealMutation.mutate({ - id: meal.id, - servings: newServings, - }); + const gridStyle: React.CSSProperties = { + display: "grid", + gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`, + gap: "24px", + width: "100%", }; - const handleIncreaseQuantity = ( - e: React.MouseEvent, - meal: LoggedMealJoinedWithNutrition, - ) => { - e.stopPropagation(); - const newServings = meal.servings + 0.5; - handleAdjustQuantity(meal, newServings); - }; + const cardBase = + "bg-sky-100 rounded-xl px-4 flex flex-row items-center justify-between h-36 min-w-0"; - const handleDecreaseQuantity = ( - e: React.MouseEvent, - meal: LoggedMealJoinedWithNutrition, - ) => { - e.stopPropagation(); - const newServings = Math.max(0.5, meal.servings - 0.5); - handleAdjustQuantity(meal, newServings); - }; - - const removeBtnOnClick = (e: React.MouseEvent, id: string | null) => { - e.preventDefault(); - if (!id) return; - - deleteMealMutation.mutate({ id }); - }; + const labelBase = "text-3xl text-sky-700 font-medium pl-3 truncate min-w-0"; return ( -
-
{dateString}
-
-
-
Calories
+
+
+ Calories +
-
-
Protein
+
+ +
+ Protein +
-
-
Carbs
+
+ +
+ Carbs +
-
-
Fat
+
+ +
+ Fat +
-
- {mealsEaten?.map((meal) => ( -
-
-

- {meal.servings} serving{meal.servings > 1 ? "s" : ""} of{" "} - {meal.dishName} -

-

- {Math.round(meal.calories * meal.servings)} calories |  - {Math.round(meal.protein * meal.servings)}g protein |  - {Math.round(meal.carbs * meal.servings)}g carbs |  - {Math.round(meal.fat * meal.servings)}g fat -

-
- - - handleIncreaseQuantity(e, meal)} - sx={{ - border: "1px solid", - borderColor: "divider", - borderRadius: "4px", - padding: "2px", - }} - > - - - handleDecreaseQuantity(e, meal)} - disabled={meal.servings <= 0.5} - sx={{ - border: "1px solid", - borderColor: "divider", - borderRadius: "4px", - padding: "2px", - }} - > - - - - - removeBtnOnClick(e, meal.id)} - sx={{ - border: "1px solid", - borderColor: "error.main", - borderRadius: "6px", - "&:hover": { - backgroundColor: "error.main", - color: "white", - }, - }} - > - - -
- ))} -
); }; export default NutritionBreakdown; +export type { NutritionData, LoggedMealJoinedWithNutrition }; +export { compileMealData }; diff --git a/apps/next/src/components/ui/nutrition-goals.tsx b/apps/next/src/components/ui/nutrition-goals.tsx new file mode 100644 index 00000000..4e7456ec --- /dev/null +++ b/apps/next/src/components/ui/nutrition-goals.tsx @@ -0,0 +1,223 @@ +"use client"; + +import EditIcon from "@mui/icons-material/Edit"; +import { Button, Drawer } from "@mui/material"; +import { useEffect, useRef, useState } from "react"; +import { useMediaQuery } from "@/hooks/useMediaQuery"; +import { trpc } from "@/utils/trpc"; + +interface Props { + userId: string; + date?: string; +} + +export default function NutritionGoals({ userId, date }: Props) { + const isMobile = useMediaQuery("(max-width: 768px)"); + const utils = trpc.useUtils(); + + const { data: defaultGoals } = trpc.nutrition.getGoals.useQuery({ userId }); + const { data: dayGoals } = trpc.nutrition.getGoalsByDay.useQuery( + { userId, date: date ?? "" }, + { enabled: !!date }, + ); + const goals = dayGoals ?? defaultGoals; + + const upsertGoals = trpc.nutrition.upsertGoals.useMutation({ + onSuccess: () => utils.nutrition.invalidate(), + }); + + const upsertGoalsByDay = trpc.nutrition.upsertGoalsByDay.useMutation({ + onSuccess: () => utils.nutrition.invalidate(), + }); + + const [open, setOpen] = useState(false); + const [calorieGoal, setCalorieGoal] = useState(2000); + const [proteinGoal, setProteinGoal] = useState(100); + const [carbGoal, setCarbGoal] = useState(250); + const [fatGoal, setFatGoal] = useState(50); + const containerRef = useRef(null); + + useEffect(() => { + if (goals) { + setCalorieGoal(goals.calorieGoal); + setProteinGoal(goals.proteinGoal); + setCarbGoal(goals.carbGoal); + setFatGoal(goals.fatGoal); + } + }, [goals]); + + useEffect(() => { + if (isMobile || !open) return; + const handleClickOutside = (e: MouseEvent) => { + if ( + containerRef.current && + !containerRef.current.contains(e.target as Node) + ) { + setOpen(false); + } + }; + document.addEventListener("click", handleClickOutside); + return () => document.removeEventListener("click", handleClickOutside); + }, [open, isMobile]); + + const handleUpdate = (updates: { + calorieGoal?: number; + proteinGoal?: number; + carbGoal?: number; + fatGoal?: number; + }) => { + const merged = { + calorieGoal, + proteinGoal, + carbGoal, + fatGoal, + ...updates, + }; + if (date) { + upsertGoalsByDay.mutate({ userId, date, ...merged }); + } else { + upsertGoals.mutate({ userId, ...merged }); + } + }; + + const inputs = ( + <> + + + + + + ); + + return ( +
+ + + {isMobile ? ( + setOpen(false)}> +
{inputs}
+
+ ) : ( + open && ( +
+ {inputs} +
+ ) + )} +
+ ); +} diff --git a/apps/next/src/components/ui/toolbar.tsx b/apps/next/src/components/ui/toolbar.tsx index 6fa4d687..3cf792f5 100644 --- a/apps/next/src/components/ui/toolbar.tsx +++ b/apps/next/src/components/ui/toolbar.tsx @@ -78,6 +78,10 @@ const DESKTOP_TOOLBAR_ELEMENTS: ToolbarElement[] = [ title: "My Foods", href: "/my-foods", }, + { + title: "Tracker", + href: "/nutrition", + }, ]; const MOBILE_TOOLBAR_ELEMENTS: ToolbarElement[] = [ diff --git a/apps/next/src/components/ui/tracker-history-dialog.tsx b/apps/next/src/components/ui/tracker-history-dialog.tsx new file mode 100644 index 00000000..0dc6a747 --- /dev/null +++ b/apps/next/src/components/ui/tracker-history-dialog.tsx @@ -0,0 +1,97 @@ +"use client"; + +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, +} from "@mui/material"; +import MobileCalorieCard from "@/components/ui/mobile-calorie-card"; +import MobileNutritionBars from "@/components/ui/mobile-nutrition-bars"; +import type { LoggedMealJoinedWithNutrition } from "@/components/ui/nutrition-breakdown"; +import TrackerHistoryMealTable from "@/components/ui/tracker-history-meal-table"; + +interface Props { + open: boolean; + onClose: () => void; + selectedDate: Date | null; + allMeals: LoggedMealJoinedWithNutrition[]; + calorieGoal: number; + proteinGoal: number; + carbGoal: number; + fatGoal: number; + userId: string; +} + +export default function TrackerHistoryDialog({ + open, + onClose, + selectedDate, + allMeals, + calorieGoal, + proteinGoal, + carbGoal, + fatGoal, + userId, +}: Props) { + const mealsForDay = allMeals.filter((m) => + selectedDate + ? new Date(m.eatenAt).toDateString() === selectedDate.toDateString() + : false, + ); + + const dateLabel = selectedDate + ? selectedDate.toLocaleDateString("en-US", { + weekday: "long", + month: "long", + day: "numeric", + year: "numeric", + }) + : ""; + + return ( + + + What you ate on {dateLabel} + + +
+ + +
+ +
+ + + + +
+ ); +} diff --git a/apps/next/src/components/ui/tracker-history-drawer.tsx b/apps/next/src/components/ui/tracker-history-drawer.tsx new file mode 100644 index 00000000..6b69a88a --- /dev/null +++ b/apps/next/src/components/ui/tracker-history-drawer.tsx @@ -0,0 +1,89 @@ +"use client"; + +import CloseIcon from "@mui/icons-material/Close"; +import { Drawer, IconButton } from "@mui/material"; +import MobileCalorieCard from "@/components/ui/mobile-calorie-card"; +import MobileNutritionBars from "@/components/ui/mobile-nutrition-bars"; +import type { LoggedMealJoinedWithNutrition } from "@/components/ui/nutrition-breakdown"; +import TrackerHistoryMealTable from "@/components/ui/tracker-history-meal-table"; + +interface Props { + open: boolean; + onClose: () => void; + selectedDate: Date | null; + allMeals: LoggedMealJoinedWithNutrition[]; + calorieGoal: number; + proteinGoal: number; + carbGoal: number; + fatGoal: number; + userId: string; +} + +export default function TrackerHistoryDrawer({ + open, + onClose, + selectedDate, + allMeals, + calorieGoal, + proteinGoal, + carbGoal, + fatGoal, + userId, +}: Props) { + const mealsForDay = allMeals.filter((m) => + selectedDate + ? new Date(m.eatenAt).toDateString() === selectedDate.toDateString() + : false, + ); + + const dateLabel = selectedDate + ? selectedDate.toLocaleDateString("en-US", { + weekday: "long", + month: "long", + day: "numeric", + year: "numeric", + }) + : ""; + + return ( + +
+
+ + What you ate on {dateLabel} + + + + +
+ + + + + + +
+
+ ); +} diff --git a/apps/next/src/components/ui/tracker-history-meal-table.tsx b/apps/next/src/components/ui/tracker-history-meal-table.tsx new file mode 100644 index 00000000..f3bba948 --- /dev/null +++ b/apps/next/src/components/ui/tracker-history-meal-table.tsx @@ -0,0 +1,61 @@ +"use client"; + +import type { LoggedMealJoinedWithNutrition } from "./nutrition-breakdown"; +import { compileMealData } from "./nutrition-breakdown"; + +interface Props { + mealsEaten: LoggedMealJoinedWithNutrition[]; +} + +export default function TrackerHistoryMealTable({ mealsEaten }: Props) { + const totals = compileMealData(mealsEaten); + + return ( +
+ + + + + + + + + + + + {mealsEaten.map((meal) => ( + + + + + + + + ))} + + + + + + + + + +
FoodCaloriesProteinCarbsFat
+
+ {meal.dishName} + + {meal.servings} + +
+
+ {Math.round(meal.calories * meal.servings)} + + {Math.round(meal.protein * meal.servings)}g + + {Math.round(meal.carbs * meal.servings)}g + {Math.round(meal.fat * meal.servings)}g
+ {totals.calories}{totals.protein_g}g{totals.carbs_g}g{totals.fat_g}g
+
+ ); +} diff --git a/apps/next/src/components/ui/tracker-history.tsx b/apps/next/src/components/ui/tracker-history.tsx new file mode 100644 index 00000000..2e3325a2 --- /dev/null +++ b/apps/next/src/components/ui/tracker-history.tsx @@ -0,0 +1,93 @@ +"use client"; + +import RestoreIcon from "@mui/icons-material/Restore"; +import { Button, Drawer } from "@mui/material"; +import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns"; +import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; +import { StaticDatePicker } from "@mui/x-date-pickers/StaticDatePicker"; +import { useEffect, useRef, useState } from "react"; +import { useMediaQuery } from "@/hooks/useMediaQuery"; + +interface Props { + onDateSelect: (date: Date) => void; + onDayClick: (date: Date) => void; + loggedDates: Date[]; +} + +export default function TrackerHistory({ + onDateSelect, + onDayClick, + loggedDates, +}: Props) { + const isMobile = useMediaQuery("(max-width: 768px)"); + const [open, setOpen] = useState(false); + const [selectedDate, setSelectedDate] = useState(null); + const containerRef = useRef(null); + + useEffect(() => { + if (isMobile || !open) return; + + const handleClickOutside = (e: MouseEvent) => { + const target = e.target as Node; + const isInsideDialog = document + .querySelector(".MuiDialog-root") + ?.contains(target); + if ( + containerRef.current && + !containerRef.current.contains(target) && + !isInsideDialog + ) { + setOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [open, isMobile]); + + const calendar = ( + + { + setSelectedDate(date ?? null); + if (date) { + onDateSelect(date); + onDayClick(date); + } + }} + shouldDisableDate={(date) => + !loggedDates.some((d) => d.toDateString() === date.toDateString()) + } + displayStaticWrapperAs="desktop" + slotProps={{ actionBar: { actions: [] } }} + sx={{ "& .MuiDateCalendar-root": { height: "300px" } }} + /> + + ); + + return ( +
+ + + {isMobile ? ( + setOpen(false)}> +
{calendar}
+
+ ) : ( + open && ( +
+ {calendar} +
+ ) + )} +
+ ); +} diff --git a/packages/api/src/nutrition/router.ts b/packages/api/src/nutrition/router.ts index cbf59fe9..f2ee5eda 100644 --- a/packages/api/src/nutrition/router.ts +++ b/packages/api/src/nutrition/router.ts @@ -1,7 +1,12 @@ import { createTRPCRouter, publicProcedure } from "@api/trpc"; -import { loggedMeals, nutritionInfos } from "@peterplate/db"; +import { + loggedMeals, + nutritionInfos, + userGoals, + userGoalsByDay, +} from "@peterplate/db"; import { TRPCError } from "@trpc/server"; -import { and, desc, eq, gt } from "drizzle-orm"; +import { and, desc, eq, gt, gte, lt } from "drizzle-orm"; import { z } from "zod"; const LoggedMealSchema = z.object({ @@ -19,6 +24,52 @@ export const nutritionRouter = createTRPCRouter({ logMeal: publicProcedure .input(LoggedMealSchema) .mutation(async ({ ctx, input }) => { + // Check if the user has already logged this dish today + const eatenAt = input.eatenAt ?? new Date(); + + const startOfDay = new Date(eatenAt); + startOfDay.setHours(0, 0, 0, 0); + + const startOfNextDay = new Date(startOfDay); + startOfNextDay.setDate(startOfNextDay.getDate() + 1); + + const existing = await ctx.db + .select({ + id: loggedMeals.id, + servings: loggedMeals.servings, + }) + .from(loggedMeals) + .where( + and( + eq(loggedMeals.userId, input.userId), + eq(loggedMeals.dishId, input.dishId), + gte(loggedMeals.eatenAt, startOfDay), + lt(loggedMeals.eatenAt, startOfNextDay), + ), + ) + .limit(1); + + // If already logged today, update servings instead of creating a new entry + if (existing[0]) { + const updated = await ctx.db + .update(loggedMeals) + .set({ + servings: existing[0].servings + input.servings, + }) + .where(eq(loggedMeals.id, existing[0].id)) + .returning(); + + if (!updated[0]) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to update logged meal", + }); + } + + return updated[0]; + } + + // Log entry as usual const result = await ctx.db .insert(loggedMeals) .values({ @@ -26,7 +77,7 @@ export const nutritionRouter = createTRPCRouter({ dishId: input.dishId, dishName: input.dishName, servings: input.servings, - eatenAt: input.eatenAt ?? new Date(), + eatenAt, }) .returning(); @@ -123,4 +174,95 @@ export const nutritionRouter = createTRPCRouter({ return result[0]; }), + + getGoals: publicProcedure + .input(z.object({ userId: z.string() })) + .query(async ({ ctx, input }) => { + const result = await ctx.db.query.userGoals.findFirst({ + where: (userGoals, { eq }) => eq(userGoals.userId, input.userId), + }); + return result ?? null; + }), + + upsertGoals: publicProcedure + .input( + z.object({ + userId: z.string(), + calorieGoal: z.number().min(100).max(10000), + proteinGoal: z.number().min(1).max(500), + carbGoal: z.number().min(1).max(1000), + fatGoal: z.number().min(1).max(500), + }), + ) + .mutation(async ({ ctx, input }) => { + const result = await ctx.db + .insert(userGoals) + .values(input) + .onConflictDoUpdate({ + target: userGoals.userId, + set: { + calorieGoal: input.calorieGoal, + proteinGoal: input.proteinGoal, + carbGoal: input.carbGoal, + fatGoal: input.fatGoal, + }, + }) + .returning(); + + return result[0]; + }), + + getGoalsByDay: publicProcedure + .input(z.object({ userId: z.string(), date: z.string() })) + .query(async ({ ctx, input }) => { + const dayGoal = await ctx.db + .select() + .from(userGoalsByDay) + .where( + and( + eq(userGoalsByDay.userId, input.userId), + eq(userGoalsByDay.date, input.date), + ), + ) + .limit(1); + + if (dayGoal[0]) return dayGoal[0]; + + const defaultGoal = await ctx.db + .select() + .from(userGoals) + .where(eq(userGoals.userId, input.userId)) + .limit(1); + + return defaultGoal[0] ?? null; + }), + + upsertGoalsByDay: publicProcedure + .input( + z.object({ + userId: z.string(), + date: z.string(), + calorieGoal: z.number().min(100).max(10000), + proteinGoal: z.number().min(1).max(500), + carbGoal: z.number().min(1).max(1000), + fatGoal: z.number().min(1).max(500), + }), + ) + .mutation(async ({ ctx, input }) => { + const result = await ctx.db + .insert(userGoalsByDay) + .values(input) + .onConflictDoUpdate({ + target: [userGoalsByDay.userId, userGoalsByDay.date], + set: { + calorieGoal: input.calorieGoal, + proteinGoal: input.proteinGoal, + carbGoal: input.carbGoal, + fatGoal: input.fatGoal, + }, + }) + .returning(); + + return result[0]; + }), }); diff --git a/packages/db/drizzle.config.ts b/packages/db/drizzle.config.ts index 56167971..73c80665 100644 --- a/packages/db/drizzle.config.ts +++ b/packages/db/drizzle.config.ts @@ -30,6 +30,8 @@ export default defineConfig({ "./src/schema/loggedMeals.ts", "./src/schema/userAllergies.ts", "./src/schema/userDietaryPreferences.ts", + "./src/schema/userGoals.ts", + "./src/schema/userGoalsByDay.ts", ], dbCredentials: { url: process.env.DATABASE_URL, ssl: false }, verbose: !process.env.CI, diff --git a/packages/db/migrations/0007_peterplate.sql b/packages/db/migrations/0007_peterplate.sql new file mode 100644 index 00000000..943879f6 --- /dev/null +++ b/packages/db/migrations/0007_peterplate.sql @@ -0,0 +1,9 @@ +CREATE TABLE "user_goals" ( + "user_id" text PRIMARY KEY NOT NULL, + "calorie_goal" integer DEFAULT 2000 NOT NULL, + "protein_goal" integer DEFAULT 75 NOT NULL, + "carb_goal" integer DEFAULT 250 NOT NULL, + "fat_goal" integer DEFAULT 50 NOT NULL +); +--> statement-breakpoint +ALTER TABLE "user_goals" ADD CONSTRAINT "user_goals_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/packages/db/migrations/0008_peterplate.sql b/packages/db/migrations/0008_peterplate.sql new file mode 100644 index 00000000..7a25f47b --- /dev/null +++ b/packages/db/migrations/0008_peterplate.sql @@ -0,0 +1 @@ +ALTER TABLE "user_goals" ALTER COLUMN "protein_goal" SET DEFAULT 100; \ No newline at end of file diff --git a/packages/db/migrations/0009_peterplate.sql b/packages/db/migrations/0009_peterplate.sql new file mode 100644 index 00000000..c94bcccb --- /dev/null +++ b/packages/db/migrations/0009_peterplate.sql @@ -0,0 +1,11 @@ +CREATE TABLE "user_goals_by_day" ( + "user_id" text NOT NULL, + "date" date NOT NULL, + "calorie_goal" integer DEFAULT 2000 NOT NULL, + "protein_goal" integer DEFAULT 100 NOT NULL, + "carb_goal" integer DEFAULT 250 NOT NULL, + "fat_goal" integer DEFAULT 50 NOT NULL, + CONSTRAINT "user_goals_by_day_user_id_date_pk" PRIMARY KEY("user_id","date") +); +--> statement-breakpoint +ALTER TABLE "user_goals_by_day" ADD CONSTRAINT "user_goals_by_day_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/packages/db/migrations/meta/0007_snapshot.json b/packages/db/migrations/meta/0007_snapshot.json new file mode 100644 index 00000000..3e93bfcc --- /dev/null +++ b/packages/db/migrations/meta/0007_snapshot.json @@ -0,0 +1,1797 @@ +{ + "id": "479afc2d-ff9f-45f9-ab19-10780e86f815", + "prevId": "769f654f-23bb-45b0-9b1e-f26d974e70db", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.contributors": { + "name": "contributors", + "schema": "", + "columns": { + "login": { + "name": "login", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "contributions": { + "name": "contributions", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'PeterPlate Contributor'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.diet_restrictions": { + "name": "diet_restrictions", + "schema": "", + "columns": { + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "contains_eggs": { + "name": "contains_eggs", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_fish": { + "name": "contains_fish", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_milk": { + "name": "contains_milk", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_peanuts": { + "name": "contains_peanuts", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_sesame": { + "name": "contains_sesame", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_shellfish": { + "name": "contains_shellfish", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_soy": { + "name": "contains_soy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_tree_nuts": { + "name": "contains_tree_nuts", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_wheat": { + "name": "contains_wheat", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_gluten_free": { + "name": "is_gluten_free", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_halal": { + "name": "is_halal", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_kosher": { + "name": "is_kosher", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_locally_grown": { + "name": "is_locally_grown", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_organic": { + "name": "is_organic", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_vegan": { + "name": "is_vegan", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_vegetarian": { + "name": "is_vegetarian", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "diet_restrictions_dish_id_dishes_id_fk": { + "name": "diet_restrictions_dish_id_dishes_id_fk", + "tableFrom": "diet_restrictions", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dishes": { + "name": "dishes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "station_id": { + "name": "station_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ingredients": { + "name": "ingredients", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'Ingredient Statement Not Available'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Other'" + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "num_ratings": { + "name": "num_ratings", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_rating": { + "name": "total_rating", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dishes_station_id_idx": { + "name": "dishes_station_id_idx", + "columns": [ + { + "expression": "station_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dishes_name_idx": { + "name": "dishes_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dishes_category_idx": { + "name": "dishes_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dishes_station_id_stations_id_fk": { + "name": "dishes_station_id_stations_id_fk", + "tableFrom": "dishes", + "tableTo": "stations", + "columnsFrom": [ + "station_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "long_description": { + "name": "long_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "end": { + "name": "end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "events_restaurant_id_idx": { + "name": "events_restaurant_id_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "events_restaurant_id_restaurants_id_fk": { + "name": "events_restaurant_id_restaurants_id_fk", + "tableFrom": "events", + "tableTo": "restaurants", + "columnsFrom": [ + "restaurant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "events_pk": { + "name": "events_pk", + "columns": [ + "title", + "restaurant_id", + "start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.favorites": { + "name": "favorites", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "favorites_user_id_users_id_fk": { + "name": "favorites_user_id_users_id_fk", + "tableFrom": "favorites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "favorites_dish_id_dishes_id_fk": { + "name": "favorites_dish_id_dishes_id_fk", + "tableFrom": "favorites", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "pins_pk": { + "name": "pins_pk", + "columns": [ + "user_id", + "dish_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dishes_to_menus": { + "name": "dishes_to_menus", + "schema": "", + "columns": { + "menu_id": { + "name": "menu_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "dishes_to_menus_menu_id_menus_id_fk": { + "name": "dishes_to_menus_menu_id_menus_id_fk", + "tableFrom": "dishes_to_menus", + "tableTo": "menus", + "columnsFrom": [ + "menu_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "dishes_to_menus_dish_id_dishes_id_fk": { + "name": "dishes_to_menus_dish_id_dishes_id_fk", + "tableFrom": "dishes_to_menus", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "dishes_to_menus_pk": { + "name": "dishes_to_menus_pk", + "columns": [ + "menu_id", + "dish_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.menus": { + "name": "menus", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "period_id": { + "name": "period_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "price": { + "name": "price", + "type": "numeric(6, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "menus_restaurant_date_idx": { + "name": "menus_restaurant_date_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "menus_restaurant_id_idx": { + "name": "menus_restaurant_id_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "menus_date_idx": { + "name": "menus_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "menus_period_id_idx": { + "name": "menus_period_id_idx", + "columns": [ + { + "expression": "period_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "menus_restaurant_id_restaurants_id_fk": { + "name": "menus_restaurant_id_restaurants_id_fk", + "tableFrom": "menus", + "tableTo": "restaurants", + "columnsFrom": [ + "restaurant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "menus_period_id_date_restaurant_id_periods_id_date_restaurant_id_fk": { + "name": "menus_period_id_date_restaurant_id_periods_id_date_restaurant_id_fk", + "tableFrom": "menus", + "tableTo": "periods", + "columnsFrom": [ + "period_id", + "date", + "restaurant_id" + ], + "columnsTo": [ + "id", + "date", + "restaurant_id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "menus_price_nonnegative": { + "name": "menus_price_nonnegative", + "value": "price IS NULL OR price >= 0" + } + }, + "isRLSEnabled": false + }, + "public.nutrition_infos": { + "name": "nutrition_infos", + "schema": "", + "columns": { + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serving_size": { + "name": "serving_size", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serving_unit": { + "name": "serving_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "calories": { + "name": "calories", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_fat_g": { + "name": "total_fat_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "trans_fat_g": { + "name": "trans_fat_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "saturated_fat_g": { + "name": "saturated_fat_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cholesterol_mg": { + "name": "cholesterol_mg", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "sodium_mg": { + "name": "sodium_mg", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_carbs_g": { + "name": "total_carbs_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "dietary_fiber_g": { + "name": "dietary_fiber_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "sugars_g": { + "name": "sugars_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "protein_g": { + "name": "protein_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "calcium": { + "name": "calcium", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "iron": { + "name": "iron", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "vitamin_a": { + "name": "vitamin_a", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "vitamin_c": { + "name": "vitamin_c", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "nutrition_infos_dish_id_dishes_id_fk": { + "name": "nutrition_infos_dish_id_dishes_id_fk", + "tableFrom": "nutrition_infos", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.logged_meals": { + "name": "logged_meals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_name": { + "name": "dish_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "servings": { + "name": "servings", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "eaten_at": { + "name": "eaten_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "logged_meals_user_id_users_id_fk": { + "name": "logged_meals_user_id_users_id_fk", + "tableFrom": "logged_meals", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "logged_meals_dish_id_dishes_id_fk": { + "name": "logged_meals_dish_id_dishes_id_fk", + "tableFrom": "logged_meals", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "servings_is_valid": { + "name": "servings_is_valid", + "value": "((\"logged_meals\".\"servings\" * 2) = floor(\"logged_meals\".\"servings\" * 2)) AND (\"logged_meals\".\"servings\" >= 0.5)" + } + }, + "isRLSEnabled": false + }, + "public.periods": { + "name": "periods", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "start": { + "name": "start", + "type": "time", + "primaryKey": false, + "notNull": true + }, + "end": { + "name": "end", + "type": "time", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "periods_restaurant_date_idx": { + "name": "periods_restaurant_date_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "periods_restaurant_id_restaurants_id_fk": { + "name": "periods_restaurant_id_restaurants_id_fk", + "tableFrom": "periods", + "tableTo": "restaurants", + "columnsFrom": [ + "restaurant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "periods_pk": { + "name": "periods_pk", + "columns": [ + "id", + "date", + "restaurant_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.push_tokens": { + "name": "push_tokens", + "schema": "", + "columns": { + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ratings": { + "name": "ratings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rating": { + "name": "rating", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "ratings_user_id_users_id_fk": { + "name": "ratings_user_id_users_id_fk", + "tableFrom": "ratings", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "ratings_dish_id_dishes_id_fk": { + "name": "ratings_dish_id_dishes_id_fk", + "tableFrom": "ratings", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "ratings_pk": { + "name": "ratings_pk", + "columns": [ + "user_id", + "dish_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurants": { + "name": "restaurants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "restaurant_name_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.stations": { + "name": "stations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "stations_restaurant_id_idx": { + "name": "stations_restaurant_id_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stations_restaurant_id_restaurants_id_fk": { + "name": "stations_restaurant_id_restaurants_id_fk", + "tableFrom": "stations", + "tableTo": "restaurants", + "columnsFrom": [ + "restaurant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "hasOnboarded": { + "name": "hasOnboarded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "accountId": { + "name": "accountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idToken": { + "name": "idToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessTokenExpiresAt": { + "name": "accessTokenExpiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refreshTokenExpiresAt": { + "name": "refreshTokenExpiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_users_id_fk": { + "name": "account_userId_users_id_fk", + "tableFrom": "account", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_users_id_fk": { + "name": "session_userId_users_id_fk", + "tableFrom": "session", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_allergies": { + "name": "user_allergies", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allergy": { + "name": "allergy", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_allergies_userId_users_id_fk": { + "name": "user_allergies_userId_users_id_fk", + "tableFrom": "user_allergies", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "user_allergies_userId_allergy_pk": { + "name": "user_allergies_userId_allergy_pk", + "columns": [ + "userId", + "allergy" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_dietary_preferences": { + "name": "user_dietary_preferences", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "preference": { + "name": "preference", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_dietary_preferences_userId_users_id_fk": { + "name": "user_dietary_preferences_userId_users_id_fk", + "tableFrom": "user_dietary_preferences", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "user_dietary_preferences_userId_preference_pk": { + "name": "user_dietary_preferences_userId_preference_pk", + "columns": [ + "userId", + "preference" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_goals": { + "name": "user_goals", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "calorie_goal": { + "name": "calorie_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2000 + }, + "protein_goal": { + "name": "protein_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 75 + }, + "carb_goal": { + "name": "carb_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 250 + }, + "fat_goal": { + "name": "fat_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 50 + } + }, + "indexes": {}, + "foreignKeys": { + "user_goals_user_id_users_id_fk": { + "name": "user_goals_user_id_users_id_fk", + "tableFrom": "user_goals", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.restaurant_id_enum": { + "name": "restaurant_id_enum", + "schema": "public", + "values": [ + "anteatery", + "brandywine" + ] + }, + "public.restaurant_name_enum": { + "name": "restaurant_name_enum", + "schema": "public", + "values": [ + "anteatery", + "brandywine" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/migrations/meta/0008_snapshot.json b/packages/db/migrations/meta/0008_snapshot.json new file mode 100644 index 00000000..b92d3cf9 --- /dev/null +++ b/packages/db/migrations/meta/0008_snapshot.json @@ -0,0 +1,1797 @@ +{ + "id": "48495b2f-52cf-4bcd-9122-d1059e5b610c", + "prevId": "479afc2d-ff9f-45f9-ab19-10780e86f815", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.contributors": { + "name": "contributors", + "schema": "", + "columns": { + "login": { + "name": "login", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "contributions": { + "name": "contributions", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'PeterPlate Contributor'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.diet_restrictions": { + "name": "diet_restrictions", + "schema": "", + "columns": { + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "contains_eggs": { + "name": "contains_eggs", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_fish": { + "name": "contains_fish", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_milk": { + "name": "contains_milk", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_peanuts": { + "name": "contains_peanuts", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_sesame": { + "name": "contains_sesame", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_shellfish": { + "name": "contains_shellfish", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_soy": { + "name": "contains_soy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_tree_nuts": { + "name": "contains_tree_nuts", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_wheat": { + "name": "contains_wheat", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_gluten_free": { + "name": "is_gluten_free", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_halal": { + "name": "is_halal", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_kosher": { + "name": "is_kosher", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_locally_grown": { + "name": "is_locally_grown", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_organic": { + "name": "is_organic", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_vegan": { + "name": "is_vegan", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_vegetarian": { + "name": "is_vegetarian", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "diet_restrictions_dish_id_dishes_id_fk": { + "name": "diet_restrictions_dish_id_dishes_id_fk", + "tableFrom": "diet_restrictions", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dishes": { + "name": "dishes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "station_id": { + "name": "station_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ingredients": { + "name": "ingredients", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'Ingredient Statement Not Available'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Other'" + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "num_ratings": { + "name": "num_ratings", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_rating": { + "name": "total_rating", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dishes_station_id_idx": { + "name": "dishes_station_id_idx", + "columns": [ + { + "expression": "station_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dishes_name_idx": { + "name": "dishes_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dishes_category_idx": { + "name": "dishes_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dishes_station_id_stations_id_fk": { + "name": "dishes_station_id_stations_id_fk", + "tableFrom": "dishes", + "tableTo": "stations", + "columnsFrom": [ + "station_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "long_description": { + "name": "long_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "end": { + "name": "end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "events_restaurant_id_idx": { + "name": "events_restaurant_id_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "events_restaurant_id_restaurants_id_fk": { + "name": "events_restaurant_id_restaurants_id_fk", + "tableFrom": "events", + "tableTo": "restaurants", + "columnsFrom": [ + "restaurant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "events_pk": { + "name": "events_pk", + "columns": [ + "title", + "restaurant_id", + "start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.favorites": { + "name": "favorites", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "favorites_user_id_users_id_fk": { + "name": "favorites_user_id_users_id_fk", + "tableFrom": "favorites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "favorites_dish_id_dishes_id_fk": { + "name": "favorites_dish_id_dishes_id_fk", + "tableFrom": "favorites", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "pins_pk": { + "name": "pins_pk", + "columns": [ + "user_id", + "dish_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dishes_to_menus": { + "name": "dishes_to_menus", + "schema": "", + "columns": { + "menu_id": { + "name": "menu_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "dishes_to_menus_menu_id_menus_id_fk": { + "name": "dishes_to_menus_menu_id_menus_id_fk", + "tableFrom": "dishes_to_menus", + "tableTo": "menus", + "columnsFrom": [ + "menu_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "dishes_to_menus_dish_id_dishes_id_fk": { + "name": "dishes_to_menus_dish_id_dishes_id_fk", + "tableFrom": "dishes_to_menus", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "dishes_to_menus_pk": { + "name": "dishes_to_menus_pk", + "columns": [ + "menu_id", + "dish_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.menus": { + "name": "menus", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "period_id": { + "name": "period_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "price": { + "name": "price", + "type": "numeric(6, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "menus_restaurant_date_idx": { + "name": "menus_restaurant_date_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "menus_restaurant_id_idx": { + "name": "menus_restaurant_id_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "menus_date_idx": { + "name": "menus_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "menus_period_id_idx": { + "name": "menus_period_id_idx", + "columns": [ + { + "expression": "period_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "menus_restaurant_id_restaurants_id_fk": { + "name": "menus_restaurant_id_restaurants_id_fk", + "tableFrom": "menus", + "tableTo": "restaurants", + "columnsFrom": [ + "restaurant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "menus_period_id_date_restaurant_id_periods_id_date_restaurant_id_fk": { + "name": "menus_period_id_date_restaurant_id_periods_id_date_restaurant_id_fk", + "tableFrom": "menus", + "tableTo": "periods", + "columnsFrom": [ + "period_id", + "date", + "restaurant_id" + ], + "columnsTo": [ + "id", + "date", + "restaurant_id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "menus_price_nonnegative": { + "name": "menus_price_nonnegative", + "value": "price IS NULL OR price >= 0" + } + }, + "isRLSEnabled": false + }, + "public.nutrition_infos": { + "name": "nutrition_infos", + "schema": "", + "columns": { + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serving_size": { + "name": "serving_size", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serving_unit": { + "name": "serving_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "calories": { + "name": "calories", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_fat_g": { + "name": "total_fat_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "trans_fat_g": { + "name": "trans_fat_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "saturated_fat_g": { + "name": "saturated_fat_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cholesterol_mg": { + "name": "cholesterol_mg", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "sodium_mg": { + "name": "sodium_mg", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_carbs_g": { + "name": "total_carbs_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "dietary_fiber_g": { + "name": "dietary_fiber_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "sugars_g": { + "name": "sugars_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "protein_g": { + "name": "protein_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "calcium": { + "name": "calcium", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "iron": { + "name": "iron", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "vitamin_a": { + "name": "vitamin_a", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "vitamin_c": { + "name": "vitamin_c", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "nutrition_infos_dish_id_dishes_id_fk": { + "name": "nutrition_infos_dish_id_dishes_id_fk", + "tableFrom": "nutrition_infos", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.logged_meals": { + "name": "logged_meals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_name": { + "name": "dish_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "servings": { + "name": "servings", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "eaten_at": { + "name": "eaten_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "logged_meals_user_id_users_id_fk": { + "name": "logged_meals_user_id_users_id_fk", + "tableFrom": "logged_meals", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "logged_meals_dish_id_dishes_id_fk": { + "name": "logged_meals_dish_id_dishes_id_fk", + "tableFrom": "logged_meals", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "servings_is_valid": { + "name": "servings_is_valid", + "value": "((\"logged_meals\".\"servings\" * 2) = floor(\"logged_meals\".\"servings\" * 2)) AND (\"logged_meals\".\"servings\" >= 0.5)" + } + }, + "isRLSEnabled": false + }, + "public.periods": { + "name": "periods", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "start": { + "name": "start", + "type": "time", + "primaryKey": false, + "notNull": true + }, + "end": { + "name": "end", + "type": "time", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "periods_restaurant_date_idx": { + "name": "periods_restaurant_date_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "periods_restaurant_id_restaurants_id_fk": { + "name": "periods_restaurant_id_restaurants_id_fk", + "tableFrom": "periods", + "tableTo": "restaurants", + "columnsFrom": [ + "restaurant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "periods_pk": { + "name": "periods_pk", + "columns": [ + "id", + "date", + "restaurant_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.push_tokens": { + "name": "push_tokens", + "schema": "", + "columns": { + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ratings": { + "name": "ratings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rating": { + "name": "rating", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "ratings_user_id_users_id_fk": { + "name": "ratings_user_id_users_id_fk", + "tableFrom": "ratings", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "ratings_dish_id_dishes_id_fk": { + "name": "ratings_dish_id_dishes_id_fk", + "tableFrom": "ratings", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "ratings_pk": { + "name": "ratings_pk", + "columns": [ + "user_id", + "dish_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurants": { + "name": "restaurants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "restaurant_name_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.stations": { + "name": "stations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "stations_restaurant_id_idx": { + "name": "stations_restaurant_id_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stations_restaurant_id_restaurants_id_fk": { + "name": "stations_restaurant_id_restaurants_id_fk", + "tableFrom": "stations", + "tableTo": "restaurants", + "columnsFrom": [ + "restaurant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "hasOnboarded": { + "name": "hasOnboarded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "accountId": { + "name": "accountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idToken": { + "name": "idToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessTokenExpiresAt": { + "name": "accessTokenExpiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refreshTokenExpiresAt": { + "name": "refreshTokenExpiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_users_id_fk": { + "name": "account_userId_users_id_fk", + "tableFrom": "account", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_users_id_fk": { + "name": "session_userId_users_id_fk", + "tableFrom": "session", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_allergies": { + "name": "user_allergies", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allergy": { + "name": "allergy", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_allergies_userId_users_id_fk": { + "name": "user_allergies_userId_users_id_fk", + "tableFrom": "user_allergies", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "user_allergies_userId_allergy_pk": { + "name": "user_allergies_userId_allergy_pk", + "columns": [ + "userId", + "allergy" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_dietary_preferences": { + "name": "user_dietary_preferences", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "preference": { + "name": "preference", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_dietary_preferences_userId_users_id_fk": { + "name": "user_dietary_preferences_userId_users_id_fk", + "tableFrom": "user_dietary_preferences", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "user_dietary_preferences_userId_preference_pk": { + "name": "user_dietary_preferences_userId_preference_pk", + "columns": [ + "userId", + "preference" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_goals": { + "name": "user_goals", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "calorie_goal": { + "name": "calorie_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2000 + }, + "protein_goal": { + "name": "protein_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "carb_goal": { + "name": "carb_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 250 + }, + "fat_goal": { + "name": "fat_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 50 + } + }, + "indexes": {}, + "foreignKeys": { + "user_goals_user_id_users_id_fk": { + "name": "user_goals_user_id_users_id_fk", + "tableFrom": "user_goals", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.restaurant_id_enum": { + "name": "restaurant_id_enum", + "schema": "public", + "values": [ + "anteatery", + "brandywine" + ] + }, + "public.restaurant_name_enum": { + "name": "restaurant_name_enum", + "schema": "public", + "values": [ + "anteatery", + "brandywine" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/migrations/meta/0009_snapshot.json b/packages/db/migrations/meta/0009_snapshot.json new file mode 100644 index 00000000..46c2ff24 --- /dev/null +++ b/packages/db/migrations/meta/0009_snapshot.json @@ -0,0 +1,1872 @@ +{ + "id": "da3f5b93-99a7-499f-a340-a750256b1c58", + "prevId": "48495b2f-52cf-4bcd-9122-d1059e5b610c", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.contributors": { + "name": "contributors", + "schema": "", + "columns": { + "login": { + "name": "login", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "contributions": { + "name": "contributions", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'PeterPlate Contributor'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.diet_restrictions": { + "name": "diet_restrictions", + "schema": "", + "columns": { + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "contains_eggs": { + "name": "contains_eggs", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_fish": { + "name": "contains_fish", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_milk": { + "name": "contains_milk", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_peanuts": { + "name": "contains_peanuts", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_sesame": { + "name": "contains_sesame", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_shellfish": { + "name": "contains_shellfish", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_soy": { + "name": "contains_soy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_tree_nuts": { + "name": "contains_tree_nuts", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "contains_wheat": { + "name": "contains_wheat", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_gluten_free": { + "name": "is_gluten_free", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_halal": { + "name": "is_halal", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_kosher": { + "name": "is_kosher", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_locally_grown": { + "name": "is_locally_grown", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_organic": { + "name": "is_organic", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_vegan": { + "name": "is_vegan", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_vegetarian": { + "name": "is_vegetarian", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "diet_restrictions_dish_id_dishes_id_fk": { + "name": "diet_restrictions_dish_id_dishes_id_fk", + "tableFrom": "diet_restrictions", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dishes": { + "name": "dishes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "station_id": { + "name": "station_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ingredients": { + "name": "ingredients", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'Ingredient Statement Not Available'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Other'" + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "num_ratings": { + "name": "num_ratings", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_rating": { + "name": "total_rating", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dishes_station_id_idx": { + "name": "dishes_station_id_idx", + "columns": [ + { + "expression": "station_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dishes_name_idx": { + "name": "dishes_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dishes_category_idx": { + "name": "dishes_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dishes_station_id_stations_id_fk": { + "name": "dishes_station_id_stations_id_fk", + "tableFrom": "dishes", + "tableTo": "stations", + "columnsFrom": [ + "station_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "long_description": { + "name": "long_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "end": { + "name": "end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "events_restaurant_id_idx": { + "name": "events_restaurant_id_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "events_restaurant_id_restaurants_id_fk": { + "name": "events_restaurant_id_restaurants_id_fk", + "tableFrom": "events", + "tableTo": "restaurants", + "columnsFrom": [ + "restaurant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "events_pk": { + "name": "events_pk", + "columns": [ + "title", + "restaurant_id", + "start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.favorites": { + "name": "favorites", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "favorites_user_id_users_id_fk": { + "name": "favorites_user_id_users_id_fk", + "tableFrom": "favorites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "favorites_dish_id_dishes_id_fk": { + "name": "favorites_dish_id_dishes_id_fk", + "tableFrom": "favorites", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "pins_pk": { + "name": "pins_pk", + "columns": [ + "user_id", + "dish_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dishes_to_menus": { + "name": "dishes_to_menus", + "schema": "", + "columns": { + "menu_id": { + "name": "menu_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "dishes_to_menus_menu_id_menus_id_fk": { + "name": "dishes_to_menus_menu_id_menus_id_fk", + "tableFrom": "dishes_to_menus", + "tableTo": "menus", + "columnsFrom": [ + "menu_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "dishes_to_menus_dish_id_dishes_id_fk": { + "name": "dishes_to_menus_dish_id_dishes_id_fk", + "tableFrom": "dishes_to_menus", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "dishes_to_menus_pk": { + "name": "dishes_to_menus_pk", + "columns": [ + "menu_id", + "dish_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.menus": { + "name": "menus", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "period_id": { + "name": "period_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "price": { + "name": "price", + "type": "numeric(6, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "menus_restaurant_date_idx": { + "name": "menus_restaurant_date_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "menus_restaurant_id_idx": { + "name": "menus_restaurant_id_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "menus_date_idx": { + "name": "menus_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "menus_period_id_idx": { + "name": "menus_period_id_idx", + "columns": [ + { + "expression": "period_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "menus_restaurant_id_restaurants_id_fk": { + "name": "menus_restaurant_id_restaurants_id_fk", + "tableFrom": "menus", + "tableTo": "restaurants", + "columnsFrom": [ + "restaurant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "menus_period_id_date_restaurant_id_periods_id_date_restaurant_id_fk": { + "name": "menus_period_id_date_restaurant_id_periods_id_date_restaurant_id_fk", + "tableFrom": "menus", + "tableTo": "periods", + "columnsFrom": [ + "period_id", + "date", + "restaurant_id" + ], + "columnsTo": [ + "id", + "date", + "restaurant_id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "menus_price_nonnegative": { + "name": "menus_price_nonnegative", + "value": "price IS NULL OR price >= 0" + } + }, + "isRLSEnabled": false + }, + "public.nutrition_infos": { + "name": "nutrition_infos", + "schema": "", + "columns": { + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serving_size": { + "name": "serving_size", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serving_unit": { + "name": "serving_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "calories": { + "name": "calories", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_fat_g": { + "name": "total_fat_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "trans_fat_g": { + "name": "trans_fat_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "saturated_fat_g": { + "name": "saturated_fat_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cholesterol_mg": { + "name": "cholesterol_mg", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "sodium_mg": { + "name": "sodium_mg", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_carbs_g": { + "name": "total_carbs_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "dietary_fiber_g": { + "name": "dietary_fiber_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "sugars_g": { + "name": "sugars_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "protein_g": { + "name": "protein_g", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "calcium": { + "name": "calcium", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "iron": { + "name": "iron", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "vitamin_a": { + "name": "vitamin_a", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "vitamin_c": { + "name": "vitamin_c", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "nutrition_infos_dish_id_dishes_id_fk": { + "name": "nutrition_infos_dish_id_dishes_id_fk", + "tableFrom": "nutrition_infos", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.logged_meals": { + "name": "logged_meals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_name": { + "name": "dish_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "servings": { + "name": "servings", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "eaten_at": { + "name": "eaten_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "logged_meals_user_id_users_id_fk": { + "name": "logged_meals_user_id_users_id_fk", + "tableFrom": "logged_meals", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "logged_meals_dish_id_dishes_id_fk": { + "name": "logged_meals_dish_id_dishes_id_fk", + "tableFrom": "logged_meals", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "servings_is_valid": { + "name": "servings_is_valid", + "value": "((\"logged_meals\".\"servings\" * 2) = floor(\"logged_meals\".\"servings\" * 2)) AND (\"logged_meals\".\"servings\" >= 0.5)" + } + }, + "isRLSEnabled": false + }, + "public.periods": { + "name": "periods", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "start": { + "name": "start", + "type": "time", + "primaryKey": false, + "notNull": true + }, + "end": { + "name": "end", + "type": "time", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "periods_restaurant_date_idx": { + "name": "periods_restaurant_date_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "periods_restaurant_id_restaurants_id_fk": { + "name": "periods_restaurant_id_restaurants_id_fk", + "tableFrom": "periods", + "tableTo": "restaurants", + "columnsFrom": [ + "restaurant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "periods_pk": { + "name": "periods_pk", + "columns": [ + "id", + "date", + "restaurant_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.push_tokens": { + "name": "push_tokens", + "schema": "", + "columns": { + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ratings": { + "name": "ratings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dish_id": { + "name": "dish_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rating": { + "name": "rating", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "ratings_user_id_users_id_fk": { + "name": "ratings_user_id_users_id_fk", + "tableFrom": "ratings", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "ratings_dish_id_dishes_id_fk": { + "name": "ratings_dish_id_dishes_id_fk", + "tableFrom": "ratings", + "tableTo": "dishes", + "columnsFrom": [ + "dish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "ratings_pk": { + "name": "ratings_pk", + "columns": [ + "user_id", + "dish_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurants": { + "name": "restaurants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "restaurant_name_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.stations": { + "name": "stations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "restaurant_id_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "stations_restaurant_id_idx": { + "name": "stations_restaurant_id_idx", + "columns": [ + { + "expression": "restaurant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stations_restaurant_id_restaurants_id_fk": { + "name": "stations_restaurant_id_restaurants_id_fk", + "tableFrom": "stations", + "tableTo": "restaurants", + "columnsFrom": [ + "restaurant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "hasOnboarded": { + "name": "hasOnboarded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "accountId": { + "name": "accountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idToken": { + "name": "idToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessTokenExpiresAt": { + "name": "accessTokenExpiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refreshTokenExpiresAt": { + "name": "refreshTokenExpiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_users_id_fk": { + "name": "account_userId_users_id_fk", + "tableFrom": "account", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_users_id_fk": { + "name": "session_userId_users_id_fk", + "tableFrom": "session", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_allergies": { + "name": "user_allergies", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allergy": { + "name": "allergy", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_allergies_userId_users_id_fk": { + "name": "user_allergies_userId_users_id_fk", + "tableFrom": "user_allergies", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "user_allergies_userId_allergy_pk": { + "name": "user_allergies_userId_allergy_pk", + "columns": [ + "userId", + "allergy" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_dietary_preferences": { + "name": "user_dietary_preferences", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "preference": { + "name": "preference", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_dietary_preferences_userId_users_id_fk": { + "name": "user_dietary_preferences_userId_users_id_fk", + "tableFrom": "user_dietary_preferences", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "user_dietary_preferences_userId_preference_pk": { + "name": "user_dietary_preferences_userId_preference_pk", + "columns": [ + "userId", + "preference" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_goals": { + "name": "user_goals", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "calorie_goal": { + "name": "calorie_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2000 + }, + "protein_goal": { + "name": "protein_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "carb_goal": { + "name": "carb_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 250 + }, + "fat_goal": { + "name": "fat_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 50 + } + }, + "indexes": {}, + "foreignKeys": { + "user_goals_user_id_users_id_fk": { + "name": "user_goals_user_id_users_id_fk", + "tableFrom": "user_goals", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_goals_by_day": { + "name": "user_goals_by_day", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "calorie_goal": { + "name": "calorie_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2000 + }, + "protein_goal": { + "name": "protein_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "carb_goal": { + "name": "carb_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 250 + }, + "fat_goal": { + "name": "fat_goal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 50 + } + }, + "indexes": {}, + "foreignKeys": { + "user_goals_by_day_user_id_users_id_fk": { + "name": "user_goals_by_day_user_id_users_id_fk", + "tableFrom": "user_goals_by_day", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_goals_by_day_user_id_date_pk": { + "name": "user_goals_by_day_user_id_date_pk", + "columns": [ + "user_id", + "date" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.restaurant_id_enum": { + "name": "restaurant_id_enum", + "schema": "public", + "values": [ + "anteatery", + "brandywine" + ] + }, + "public.restaurant_name_enum": { + "name": "restaurant_name_enum", + "schema": "public", + "values": [ + "anteatery", + "brandywine" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index 23d77d4e..dd7c4413 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -50,6 +50,27 @@ "when": 1771019416483, "tag": "0006_peterplate", "breakpoints": true + }, + { + "idx": 7, + "version": "7", + "when": 1771811376183, + "tag": "0007_peterplate", + "breakpoints": true + }, + { + "idx": 8, + "version": "7", + "when": 1772726487236, + "tag": "0008_peterplate", + "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1772734641386, + "tag": "0009_peterplate", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index f71305a0..1fe5b9bd 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -5,6 +5,9 @@ import pg, { type Pool as PoolType } from "pg"; const { Pool } = pg; import * as schema from "./schema"; + +export * from "./schema/userGoalsByDay"; + export const pool = (config: PoolConfig): PoolType => new Pool(config); /** diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts index f2e7f3fc..7f0604cc 100644 --- a/packages/db/src/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -15,6 +15,8 @@ export * from "./restaurants"; export * from "./stations"; export * from "./userAllergies"; export * from "./userDietaryPreferences"; +export * from "./userGoals"; +export * from "./userGoalsByDay"; export * from "./users"; // import { drizzle } from 'drizzle-orm/node-postgres'; // or your DB driver diff --git a/packages/db/src/schema/userGoals.ts b/packages/db/src/schema/userGoals.ts new file mode 100644 index 00000000..ec971846 --- /dev/null +++ b/packages/db/src/schema/userGoals.ts @@ -0,0 +1,15 @@ +import { integer, pgTable, text } from "drizzle-orm/pg-core"; +import { users } from "./users"; + +export const userGoals = pgTable("user_goals", { + userId: text("user_id") + .primaryKey() + .references(() => users.id, { onDelete: "cascade" }), + calorieGoal: integer("calorie_goal").notNull().default(2000), + proteinGoal: integer("protein_goal").notNull().default(100), + carbGoal: integer("carb_goal").notNull().default(250), + fatGoal: integer("fat_goal").notNull().default(50), +}); + +export type InsertUserGoals = typeof userGoals.$inferInsert; +export type SelectUserGoals = typeof userGoals.$inferSelect; diff --git a/packages/db/src/schema/userGoalsByDay.ts b/packages/db/src/schema/userGoalsByDay.ts new file mode 100644 index 00000000..00ada66e --- /dev/null +++ b/packages/db/src/schema/userGoalsByDay.ts @@ -0,0 +1,19 @@ +import { date, integer, pgTable, primaryKey, text } from "drizzle-orm/pg-core"; +import { users } from "./users"; + +export const userGoalsByDay = pgTable( + "user_goals_by_day", + { + userId: text("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + date: date("date").notNull(), + calorieGoal: integer("calorie_goal").notNull().default(2000), + proteinGoal: integer("protein_goal").notNull().default(100), + carbGoal: integer("carb_goal").notNull().default(250), + fatGoal: integer("fat_goal").notNull().default(50), + }, + (table) => ({ + pk: primaryKey({ columns: [table.userId, table.date] }), + }), +);