@@ -4,7 +4,11 @@ import cron from 'node-cron';
44import { join } from 'path' ;
55
66import { PrismaClient } from '@prisma/client' ;
7- import type { CampusArea , EateryType , PaymentMethod } from '@prisma/client' ;
7+ import {
8+ type CampusArea ,
9+ EateryType ,
10+ type PaymentMethod ,
11+ } from '@prisma/client' ;
812
913import { DEFAULT_IMAGE_URL } from '../src/constants.js' ;
1014import {
@@ -18,6 +22,7 @@ import {
1822import type {
1923 RawDiningItem ,
2024 RawEatery ,
25+ RawOperatingHourEventMenuCategory ,
2126 RawScrapedData ,
2227 RawStaticEatery ,
2328} from './scraperTypes.js' ;
@@ -98,7 +103,7 @@ async function fetchFreedgeDiningItems(): Promise<RawDiningItem[]> {
98103 { signal : AbortSignal . timeout ( 10000 ) } ,
99104 ) ;
100105
101- if ( response . status > 400 ) {
106+ if ( response . status >= 400 ) {
102107 console . log ( `Failed to fetch freedge data: HTTP ${ response . status } ` ) ;
103108 return [ ] ;
104109 }
@@ -147,6 +152,28 @@ async function fetchFreedgeDiningItems(): Promise<RawDiningItem[]> {
147152 }
148153}
149154
155+ function fetchCafeMenu (
156+ diningItems : RawDiningItem [ ] ,
157+ ) : RawOperatingHourEventMenuCategory [ ] {
158+ const grouped : Record < string , RawDiningItem [ ] > = { } ;
159+
160+ for ( const item of diningItems ) {
161+ if ( ! grouped [ item . category ] ) {
162+ grouped [ item . category ] = [ ] ;
163+ }
164+
165+ grouped [ item . category ] . push ( item ) ;
166+ }
167+
168+ return Object . entries ( grouped ) . map ( ( [ category , items ] ) => ( {
169+ category,
170+ items : items . map ( ( item ) => ( {
171+ item : item . item ,
172+ healthy : item . healthy ,
173+ } ) ) ,
174+ } ) ) ;
175+ }
176+
150177function extractAnnouncements ( announcements ?: { title : string } [ ] ) : string [ ] {
151178 if ( ! Array . isArray ( announcements ) ) return [ ] ;
152179
@@ -162,11 +189,9 @@ function transformStaticEatery(rawStaticEatery: RawStaticEatery) {
162189 endTimestamp : Date ;
163190 menu : Array < {
164191 category : string ;
165- sortIdx : number ;
166192 items : Array < {
167193 item : string ;
168194 healthy : boolean ;
169- sortIdx : number ;
170195 } > ;
171196 } > ;
172197 } > = [ ] ;
@@ -184,7 +209,7 @@ function transformStaticEatery(rawStaticEatery: RawStaticEatery) {
184209 type : event . descr || 'General' ,
185210 startTimestamp : startDate ,
186211 endTimestamp : endDate ,
187- menu : event . menu || [ ] ,
212+ menu : fetchCafeMenu ( rawStaticEatery . diningItems ) || [ ] ,
188213 } ) ;
189214 }
190215 }
@@ -228,6 +253,7 @@ function transformStaticEatery(rawStaticEatery: RawStaticEatery) {
228253 new Set ( rawStaticEatery . payMethods . map ( mapPaymentMethod ) ) ,
229254 ) ,
230255 eateryTypes : rawStaticEatery . eateryTypes . map ( mapEateryType ) ,
256+ diningItems : rawStaticEatery . diningItems ,
231257 announcements : extractAnnouncements ( rawStaticEatery . announcements ) ,
232258 } ,
233259 events,
@@ -249,7 +275,7 @@ function transformEatery(rawEatery: RawEatery) {
249275 about : rawEatery . about ,
250276 shortAbout : rawEatery . aboutshort ,
251277 cornellDining : rawEatery . cornellDining ,
252- menuSummary : rawEatery . opHoursCalcDescr ,
278+ menuSummary : 'Cornell Eatery' ,
253279 imageUrl : imageUrl ,
254280 campusArea : mapCampusArea ( rawEatery . campusArea ) ,
255281 onlineOrderUrl : rawEatery . onlineOrderUrl ,
@@ -258,6 +284,7 @@ function transformEatery(rawEatery: RawEatery) {
258284 latitude : rawEatery . latitude ,
259285 longitude : rawEatery . longitude ,
260286 location : rawEatery . location ,
287+ diningItems : rawEatery . diningItems ,
261288 paymentMethods : Array . from (
262289 new Set ( rawEatery . payMethods . map ( mapPaymentMethod ) ) ,
263290 ) ,
@@ -271,31 +298,40 @@ function transformEatery(rawEatery: RawEatery) {
271298 endTimestamp : Date ;
272299 menu : Array < {
273300 category : string ;
274- sortIdx : number ;
275301 items : Array < {
276302 item : string ;
277303 healthy : boolean ;
278- sortIdx : number ;
279304 } > ;
280305 } > ;
281306 } > = [ ] ;
282307
308+ let cafeDiningItems : RawOperatingHourEventMenuCategory [ ] = [ ] ;
309+ if ( eateryData . eateryTypes . includes ( EateryType . CAFE ) ) {
310+ cafeDiningItems = fetchCafeMenu ( eateryData . diningItems ) ;
311+ }
283312 for ( const operatingHour of rawEatery . operatingHours ) {
284313 for ( const event of operatingHour . events ) {
285- events . push ( {
286- type : event . descr ,
287- startTimestamp : new Date ( event . startTimestamp * 1000 ) ,
288- endTimestamp : new Date ( event . endTimestamp * 1000 ) ,
289- menu : event . menu . map ( ( cat ) => ( {
290- category : cat . category ,
291- sortIdx : cat . sortIdx ,
292- items : cat . items . map ( ( item ) => ( {
293- item : item . item ,
294- healthy : item . healthy ,
295- sortIdx : item . sortIdx ,
314+ if ( eateryData . eateryTypes . includes ( EateryType . CAFE ) ) {
315+ events . push ( {
316+ type : event . descr ,
317+ startTimestamp : new Date ( event . startTimestamp * 1000 ) ,
318+ endTimestamp : new Date ( event . endTimestamp * 1000 ) ,
319+ menu : cafeDiningItems ,
320+ } ) ;
321+ } else {
322+ events . push ( {
323+ type : event . descr ,
324+ startTimestamp : new Date ( event . startTimestamp * 1000 ) ,
325+ endTimestamp : new Date ( event . endTimestamp * 1000 ) ,
326+ menu : event . menu . map ( ( cat ) => ( {
327+ category : cat . category ,
328+ items : cat . items . map ( ( item ) => ( {
329+ item : item . item ,
330+ healthy : item . healthy ,
331+ } ) ) ,
296332 } ) ) ,
297- } ) ) ,
298- } ) ;
333+ } ) ;
334+ }
299335 }
300336 }
301337
@@ -372,6 +408,7 @@ async function processAllEateries(
372408 location : string ;
373409 paymentMethods : PaymentMethod [ ] ;
374410 eateryTypes : EateryType [ ] ;
411+ diningItems : RawDiningItem [ ] ;
375412 announcements : string [ ] ;
376413 } ;
377414 events : Array < {
@@ -380,11 +417,9 @@ async function processAllEateries(
380417 endTimestamp : Date ;
381418 menu : Array < {
382419 category : string ;
383- sortIdx : number ;
384420 items : Array < {
385421 item : string ;
386422 healthy : boolean ;
387- sortIdx : number ;
388423 } > ;
389424 } > ;
390425 } > ;
@@ -402,10 +437,11 @@ async function processAllEateries(
402437 const batch = transformedEateries . slice ( i , i + BATCH_SIZE ) ;
403438
404439 await Promise . all (
405- batch . map ( ( { eatery, events } ) =>
406- tx . eatery . create ( {
440+ batch . map ( ( { eatery, events } ) => {
441+ const { diningItems, ...eaterySanitized } = eatery ;
442+ return tx . eatery . create ( {
407443 data : {
408- ...eatery ,
444+ ...eaterySanitized ,
409445 events : {
410446 create : events . map ( ( rawEvent ) => ( {
411447 type : mapEventType ( rawEvent . type ) ,
@@ -424,8 +460,8 @@ async function processAllEateries(
424460 } ) ) ,
425461 } ,
426462 } ,
427- } ) ,
428- ) ,
463+ } ) ;
464+ } ) ,
429465 ) ;
430466 }
431467 } ,
@@ -703,7 +739,7 @@ function startScraperScheduler() {
703739 const cronExpression = process . env . SCRAPER_CRON_SCHEDULE || '0 7,19 * * *' ;
704740
705741 console . log ( '[Scheduler] Initializing scraper scheduler...' ) ;
706- console . log ( ' [Scheduler] Schedule: Daily at 7:00 AM and 7:00 PM' ) ;
742+ console . log ( ` [Scheduler] Schedule: ${ cronExpression } (America/New_York)` ) ;
707743
708744 const task = cron . schedule ( cronExpression , runScraperSafely , {
709745 timezone : 'America/New_York' ,
0 commit comments