diff --git a/README.md b/README.md index 69490937..b2df6481 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,9 @@ A summary of the libraries we use are listed below. It's responsible for installing, uninstalling, and keeping track of the app's dependencies. `npm install --global pnpm` +3. Make sure to have `docker` installed, which will allow you to run the local postgres database + required for backend functions. You can install it from [the official website here](https://www.docker.com/get-started/). + ## Developing 1. Clone the ZotMeal repository or your fork. @@ -44,19 +47,25 @@ A summary of the libraries we use are listed below. `nvm use` 3. Navigate to the root directory and install the dependencies. `cd ZotMeal && pnpm install` -4. Create a .env based on the .env.example -5. To start a local Postgres container database run the `start-database.sh` script. This will automatically set a test database. -6. Run `turbo db:generate` -7. Start the local development servers for expo and server. - `pnpm dev` +4. To start a local Postgres container database run the `docker compose up` in the root directory. + This will automatically set up and run a test database using docker. + +5. Create a .env based on the .env.example using `localhost` + +6. Run `pnpm db:push` to push the schema to the docker database. + +7. Start the local development servers for expo and server with `pnpm dev`. The tRPC procedures are available on ?input={field: value} -8. View the local website at and/or with the [Expo Go mobile app](https://expo.dev/client). +8. View the local website at and/or with the [Expo Go mobile app](https://expo.dev/client). As you make changes to the Expo application, those changes will be automatically reflected on the local website as well as the mobile app. -9. To add a new package run `turbo gen workspace` and follow the prompts ## Testing Run `turbo test` at the root of the project. + +## Adding Workspaces + +To add a new package run `turbo gen workspace` and follow the prompts diff --git a/apps/expo/src/app/events/_layout.tsx b/apps/expo/src/app/events/_layout.tsx new file mode 100644 index 00000000..0b8d0277 --- /dev/null +++ b/apps/expo/src/app/events/_layout.tsx @@ -0,0 +1,18 @@ +import { Stack } from "expo-router" +import { EventsProvider } from "./eventsContext" + +export default function EventsLayout() { + return ( + + + + + + + ) +} \ No newline at end of file diff --git a/apps/expo/src/app/events/event/[id].tsx b/apps/expo/src/app/events/event/[id].tsx index 389ff6b2..bb3be47a 100644 --- a/apps/expo/src/app/events/event/[id].tsx +++ b/apps/expo/src/app/events/event/[id].tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useContext } from "react"; import { Stack, useGlobalSearchParams } from "expo-router"; import { CalendarClock, @@ -24,28 +24,19 @@ import type { Event } from "@zotmeal/db"; import { useMenuStore } from "~/utils"; +import { useEvents } from "../eventsContext"; + export default function Event() { const { id } = useGlobalSearchParams(); const { selectedRestaurant } = useMenuStore(); - - const testData = { - title: "Test Event", - start: new Date("2022-01-01 12:00:00"), - end: new Date(), - image: - "https://uci.campusdish.com/-/media/Feature/Articles/DefaultEventImage.ashx?mh=350&mw=350&hash=B788068F09F0E38D1D19756934E293E4C1379BBF", - shortDescription: "This is a test event with a short description!", - longDescription: `This is a long description of the event. It's so long that it wraps around multiple lines. It's a very long description, but it's also very interesting. You should definitely read it.`, - restaurantId: "3314", - } satisfies Event; + const { events } = useEvents() + const eventData = events[Number(id)]! return ( <> - -

{testData.title}

+ +

{eventData.title}

@@ -90,13 +81,13 @@ export default function Event() { - {format(testData.start.toString(), "LLL do p")} -{" "} - {format(testData.end.toString(), "LLL do p")} + {format(eventData.start.toString(), "LLL do p")} -{" "} + {format(eventData.end.toString(), "LLL do p")} - {testData.shortDescription} + {eventData.shortDescription}
@@ -124,7 +115,7 @@ export default function Event() { borderTopLeftRadius={0} borderTopRightRadius={0} > - {testData.longDescription} + {eventData.longDescription} diff --git a/apps/expo/src/app/events/eventsContext.tsx b/apps/expo/src/app/events/eventsContext.tsx new file mode 100644 index 00000000..877a71db --- /dev/null +++ b/apps/expo/src/app/events/eventsContext.tsx @@ -0,0 +1,24 @@ +import React, { createContext, useState, useContext, ReactNode } from 'react'; +import type { Event } from "@zotmeal/db" + +interface EventsContextProps { + events: Event[]; + setEvents: React.Dispatch>; +} + +const EventsContext = createContext({events: [], setEvents: () => {}}); + +export function EventsProvider(props: { children: ReactNode }) { + const [events, setEvents] = useState([]); + + return ( + + {props.children} + + ); +}; + +export function useEvents() { + const context = useContext(EventsContext); + return context; + }; \ No newline at end of file diff --git a/apps/expo/src/app/events/index.tsx b/apps/expo/src/app/events/index.tsx index ff10cd6b..5780ad63 100644 --- a/apps/expo/src/app/events/index.tsx +++ b/apps/expo/src/app/events/index.tsx @@ -1,36 +1,49 @@ +import { useEffect} from "react"; import { Link } from "expo-router"; import { format } from "date-fns"; import { H3, Image, ScrollView, Text, YStack } from "tamagui"; import type { Event } from "@zotmeal/db"; +import { useEvents } from "./eventsContext"; import { RestaurantTabs } from "~/components"; -// import { api } from "~/utils/api"; +import { api } from "~/utils/api"; + +// Create a context for events, default value is a test event +const testData: Event = { + start: new Date("2022-01-01 12:00:00"), + end: new Date(), + title: "Test Event", + shortDescription: "This is a test event", + longDescription: `This is a long description of the event. It's so long that it wraps + around multiple lines. It's a very long description, but it's also + very interesting. You should definitely read it.`, + image: + "https://uci.campusdish.com/-/media/Feature/Articles/DefaultEventImage.ashx?mh=350&mw=350&hash=B788068F09F0E38D1D19756934E293E4C1379BBF", + restaurantId: "3314", +} satisfies Event; + +// Events Component export default function Events() { - // const { data, error } = api.event.get.useQuery({}); + const { events, setEvents } = useEvents(); + + const eventsQuery = api.event.get.useQuery({}); - const testData: Event[] = Array(5).fill({ - start: new Date("2022-01-01 12:00:00"), - end: new Date(), - title: "Test Event", - shortDescription: "This is a test event", - longDescription: `This is a long description of the event. It's so long that it wraps - around multiple lines. It's a very long description, but it's also - very interesting. You should definitely read it.`, - image: - "https://uci.campusdish.com/-/media/Feature/Articles/DefaultEventImage.ashx?mh=350&mw=350&hash=B788068F09F0E38D1D19756934E293E4C1379BBF", - restaurantId: "3314", - } satisfies Event) as Event[]; + useEffect(() => { + if (eventsQuery?.data) { + setEvents(eventsQuery.data); + } + }, [eventsQuery?.data]); - // if (!data) { - // return Loading...; - // } + if (eventsQuery?.isLoading) { + return Loading...; + } - // if (error) { - // return Error: {error.message}; - // } + if (eventsQuery?.isError) { + return Error: {eventsQuery.error.message}; + } return ( - {testData.map((event: Event, index: number) => ( + {events.map((event: Event, index: number) => ( diff --git a/packages/api/src/events/services/scrape.ts b/packages/api/src/events/services/scrape.ts index 776ad447..b0cac23b 100644 --- a/packages/api/src/events/services/scrape.ts +++ b/packages/api/src/events/services/scrape.ts @@ -1,9 +1,10 @@ import axios from "axios"; import * as cheerio from "cheerio"; -import type { Event } from "@zotmeal/db"; +import type { Event, Drizzle } from "@zotmeal/db"; import { EventSchema } from "@zotmeal/db"; import { getRestaurantId, parseEventDate } from "@zotmeal/utils"; +import { upsertEvents } from "./event"; import { logger } from "../../../logger"; @@ -115,3 +116,19 @@ export async function scrapeEvents(html: string): Promise { } return null; } + +// scrapes all events from campusDish and upserts them into the db +export async function scrapeCampusDishEvents(db: Drizzle): Promise { + const html = await getHTML( + "https://uci-campusdish-com.translate.goog/api/events?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=wapp", + ); + const events = await scrapeEvents(html); + + if (!events) { + throw new Error("Could not retrieve campus dish events"); + } + + const upsertedEvents = await upsertEvents(db, events); + + return upsertedEvents +} \ No newline at end of file diff --git a/packages/api/src/schedules/getWeekInfo.ts b/packages/api/src/schedules/getWeekInfo.ts index a620f6cf..ecfd0209 100644 --- a/packages/api/src/schedules/getWeekInfo.ts +++ b/packages/api/src/schedules/getWeekInfo.ts @@ -4,6 +4,7 @@ import { z } from "zod"; import type { Drizzle } from "@zotmeal/db"; import { RestaurantSchema } from "@zotmeal/db"; import { DateRegex } from "@zotmeal/validators"; +import { scrapeCampusDishEvents } from "../events"; import type { UpdateDailyParams } from "./updateDaily"; import { logger } from "../../logger"; @@ -24,6 +25,10 @@ export async function getWeekInfo( const { date: dateString, restaurant } = params; const startDate = new Date(dateString); + // Scrape and insert new events into db + const eventResults = await scrapeCampusDishEvents(db) + + // Update menus for each day const results = await Promise.allSettled( Array.from({ length: NUM_DAYS_UPDATE }).map((_, i) => { const insertDate = new Date();