diff --git a/drizzle/0011_fancy_stryfe.sql b/drizzle/0011_fancy_stryfe.sql new file mode 100644 index 0000000..8ad39bb --- /dev/null +++ b/drizzle/0011_fancy_stryfe.sql @@ -0,0 +1,7 @@ +CREATE TABLE "config" ( + "key" text PRIMARY KEY NOT NULL, + "value" text NOT NULL +); +--> statement-breakpoint +ALTER TABLE "location_data" ADD COLUMN "grubhub_url" text;--> statement-breakpoint +ALTER TABLE "overwrites_table" ADD COLUMN "grubhub_url" text; \ No newline at end of file diff --git a/drizzle/meta/0011_snapshot.json b/drizzle/meta/0011_snapshot.json new file mode 100644 index 0000000..f888f8e --- /dev/null +++ b/drizzle/meta/0011_snapshot.json @@ -0,0 +1,1152 @@ +{ + "id": "2d593e66-bd0b-458d-ad7f-26a8cd4b65cb", + "prevId": "f2f47465-b027-4494-b217-1e6ca9c4ce8e", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.config": { + "name": "config", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.emails": { + "name": "emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "emails_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_id_to_internal_id": { + "name": "external_id_to_internal_id", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "external_id_type": { + "name": "external_id_type", + "type": "externalIdType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'concept_id'" + } + }, + "indexes": { + "internal_id": { + "name": "internal_id", + "columns": [ + { + "expression": "internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_id_to_internal_id_internal_id_location_data_id_fk": { + "name": "external_id_to_internal_id_internal_id_location_data_id_fk", + "tableFrom": "external_id_to_internal_id", + "tableTo": "location_data", + "columnsFrom": [ + "internal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "external_id_to_internal_id_external_id_unique": { + "name": "external_id_to_internal_id_external_id_unique", + "nullsNotDistinct": false, + "columns": [ + "external_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.location_data": { + "name": "location_data", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "menu": { + "name": "menu", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grubhub_url": { + "name": "grubhub_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "coordinate_lat": { + "name": "coordinate_lat", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "coordinate_lng": { + "name": "coordinate_lng", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "accepts_online_orders": { + "name": "accepts_online_orders", + "type": "boolean", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.overwrites_table": { + "name": "overwrites_table", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "menu": { + "name": "menu", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grubhub_url": { + "name": "grubhub_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "coordinate_lat": { + "name": "coordinate_lat", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "coordinate_lng": { + "name": "coordinate_lng", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "accepts_online_orders": { + "name": "accepts_online_orders", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "overwrites_table_location_id_location_data_id_fk": { + "name": "overwrites_table_location_id_location_data_id_fk", + "tableFrom": "overwrites_table", + "tableTo": "location_data", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reports": { + "name": "reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "reports_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "location_id": { + "name": "location_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "reports_user_id_users_id_fk": { + "name": "reports_user_id_users_id_fk", + "tableFrom": "reports", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reports_location_id_location_data_id_fk": { + "name": "reports_location_id_location_data_id_fk", + "tableFrom": "reports", + "tableTo": "location_data", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.specials": { + "name": "specials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "specials_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "location_id": { + "name": "location_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 + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "specialType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "specials_location_id_location_data_id_fk": { + "name": "specials_location_id_location_data_id_fk", + "tableFrom": "specials", + "tableTo": "location_data", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.star_reviews": { + "name": "star_reviews", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "star_reviews_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "star_rating": { + "name": "star_rating", + "type": "numeric(2, 1)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "star_reviews_location_user_uniq": { + "name": "star_reviews_location_user_uniq", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "star_reviews_user_id_users_id_fk": { + "name": "star_reviews_user_id_users_id_fk", + "tableFrom": "star_reviews", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "star_reviews_location_id_location_data_id_fk": { + "name": "star_reviews_location_id_location_data_id_fk", + "tableFrom": "star_reviews", + "tableTo": "location_data", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "rating_number_check": { + "name": "rating_number_check", + "value": "\"star_reviews\".\"star_rating\" > 0 AND \"star_reviews\".\"star_rating\" <= 5 AND mod(\"star_reviews\".\"star_rating\"*2,1) = 0" + } + }, + "isRLSEnabled": false + }, + "public.tag_list": { + "name": "tag_list", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tag_list_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tag_reviews": { + "name": "tag_reviews", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tag_reviews_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "tag_id": { + "name": "tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "written_review": { + "name": "written_review", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hidden": { + "name": "hidden", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tag_reviews_location_tag_user_uniq": { + "name": "tag_reviews_location_tag_user_uniq", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tag_reviews_tag_id_tag_list_id_fk": { + "name": "tag_reviews_tag_id_tag_list_id_fk", + "tableFrom": "tag_reviews", + "tableTo": "tag_list", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tag_reviews_user_id_users_id_fk": { + "name": "tag_reviews_user_id_users_id_fk", + "tableFrom": "tag_reviews", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tag_reviews_location_id_location_data_id_fk": { + "name": "tag_reviews_location_id_location_data_id_fk", + "tableFrom": "tag_reviews", + "tableTo": "location_data", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.time_overwrites_table": { + "name": "time_overwrites_table", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "time_string": { + "name": "time_string", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "time_overwrites_table_location_id_location_data_id_fk": { + "name": "time_overwrites_table_location_id_location_data_id_fk", + "tableFrom": "time_overwrites_table", + "tableTo": "location_data", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "time_overwrites_table_location_id_date_pk": { + "name": "time_overwrites_table_location_id_date_pk", + "columns": [ + "location_id", + "date" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.location_times": { + "name": "location_times", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "location_times_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "location_id": { + "name": "location_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "start_time": { + "name": "start_time", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_time": { + "name": "end_time", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "date_lookup": { + "name": "date_lookup", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "location_times_location_id_location_data_id_fk": { + "name": "location_times_location_id_location_data_id_fk", + "tableFrom": "location_times", + "tableTo": "location_data", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "users_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "google_id": { + "name": "google_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "picture_url": { + "name": "picture_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "google_id": { + "name": "google_id", + "columns": [ + { + "expression": "google_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.weekly_time_overwrites_table": { + "name": "weekly_time_overwrites_table", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "weekday": { + "name": "weekday", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "time_string": { + "name": "time_string", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "weekly_time_overwrites_table_location_id_location_data_id_fk": { + "name": "weekly_time_overwrites_table_location_id_location_data_id_fk", + "tableFrom": "weekly_time_overwrites_table", + "tableTo": "location_data", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "weekly_time_overwrites_table_location_id_weekday_pk": { + "name": "weekly_time_overwrites_table_location_id_weekday_pk", + "columns": [ + "location_id", + "weekday" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "weekday_check": { + "name": "weekday_check", + "value": "\"weekly_time_overwrites_table\".\"weekday\" >= 0 AND \"weekly_time_overwrites_table\".\"weekday\" < 7" + } + }, + "isRLSEnabled": false + } + }, + "enums": { + "public.externalIdType": { + "name": "externalIdType", + "schema": "public", + "values": [ + "concept_id" + ] + }, + "public.specialType": { + "name": "specialType", + "schema": "public", + "values": [ + "special", + "soup" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 46b2076..52182e1 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -78,6 +78,13 @@ "when": 1771716042342, "tag": "0010_shallow_doctor_strange", "breakpoints": true + }, + { + "idx": 11, + "version": "7", + "when": 1774733036668, + "tag": "0011_fancy_stryfe", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/containers/grubhubLinkIds.ts b/src/containers/grubhubLinkIds.ts new file mode 100644 index 0000000..13b1db4 --- /dev/null +++ b/src/containers/grubhubLinkIds.ts @@ -0,0 +1,29 @@ +import { IGrubhubLinkIds } from "types"; + +const grubhubLinkIds: IGrubhubLinkIds = { + "180": "-15379708", + "109": "-15379708", + "110": "-6448259", + "82": "-6012140", + "113": "-5954818", + "114": "-6012713", + "178": "-7831768", + "127": "-5980773", + "155": "-6010765", + "168": "-24807638", + "188": "-12461575", + "186": "-11345145", + "136": "-8950322", + "92": "-5980830", + "84": "-6013780", + "91": "-6013802", + "134": "-9070241", + "185": "-6479803", + "172": "-6480127", + "193": "-15546561", + "202": "-19381681", + "194": "-15221033", + "179": "-10319024", +}; + +export default grubhubLinkIds; \ No newline at end of file diff --git a/src/containers/grubhubUrlBuilder.ts b/src/containers/grubhubUrlBuilder.ts new file mode 100644 index 0000000..35a294e --- /dev/null +++ b/src/containers/grubhubUrlBuilder.ts @@ -0,0 +1,148 @@ +import { IGrubhubAuthResponse, IGrubhubData } from "types"; +import grubhubLinkIds from "./grubhubLinkIds"; +import { DBType } from "db/db"; +import { configTable } from "db/schema"; +import { eq } from "drizzle-orm"; + +const GRUBHUB_REFRESH_TOKEN_KEY = "grubhub_refresh_token"; +const DEFAULT_REFRESH_TOKEN = "e209dd9f-fbfc-442d-8f86-63b13db152cd"; + +/** + * For building the Grubhub deep links. + */ +export default class GrubhubUrlBuilder { + private readonly AUTH_URL = "https://api-gtm.grubhub.com/auth/refresh"; + private readonly API_URL = "https://api-gtm.grubhub.com/topics-gateway/v1/topic/content?applicationId=ios&location=POINT(-79.93882752%2040.44434738)&locationMode=PICKUP&position=1&operationId=ab529cdc-4546-4a51-8b08-5738c175445c&pageSource=CAMPUS&topicSource=campus/search&topicId=dd1de4dc-6a7e-4bb4-bf02-2d37f2c66217&applicationVersion=2025.40&timezoneOffset=-14400000¶meter=locationMode:PICKUP¶meter=radius:5.0&dinerLocation=POINT(-79.93882752%2040.44434738)&geohash=dppnhfwm6kcc"; + + private readonly CLIENT_ID = "ghiphone_Vkuxbs6t0f4SZjTOW42Y52z1itJ7Li0Tw3FEcboT"; + private db: DBType; + + constructor(db: DBType) { + this.db = db; + } + + public async build(): Promise> { + const accessToken = await this.refreshToken(); + const restaurantData = await this.fetchRestaurantData(accessToken); + //console.log(restaurantData); + const deepLinks = this.parseAndBuildLinks(restaurantData); + + const conceptToDeepLinks: Record = {}; + Object.entries(grubhubLinkIds).map(([concept_id, rest_id]) => { + const link = deepLinks[rest_id]; + if (link !== undefined) { + conceptToDeepLinks[concept_id] = link; + } + }); + + console.table(conceptToDeepLinks); + + return conceptToDeepLinks; + } + + private async getRefreshToken(): Promise { + const result = await this.db + .select() + .from(configTable) + .where(eq(configTable.key, GRUBHUB_REFRESH_TOKEN_KEY)); + + if (result.length > 0) { + return result[0]!.value; + } + + // Seed the default token into the DB on first use + await this.setRefreshToken(DEFAULT_REFRESH_TOKEN); + return DEFAULT_REFRESH_TOKEN; + } + + private async setRefreshToken(token: string): Promise { + await this.db + .insert(configTable) + .values({ key: GRUBHUB_REFRESH_TOKEN_KEY, value: token }) + .onConflictDoUpdate({ + target: configTable.key, + set: { value: token }, + }); + } + + private async refreshToken(): Promise { + const currentRefreshToken = await this.getRefreshToken(); + + const payload = { + refresh_token: currentRefreshToken, + client_id: this.CLIENT_ID + }; + + const response = await fetch(this.AUTH_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + throw Error(`Failed to refresh token: ${response.statusText}`); + } + + const data: IGrubhubAuthResponse = await response.json(); + if (!data.session_handle?.access_token) { + throw Error("Access token not found in refresh response"); + } + + await this.setRefreshToken(data.session_handle.refresh_token); + + return data.session_handle.access_token; + } + + private async fetchRestaurantData(token: string): Promise { + const headers = { + 'Authorization': `Bearer ${token}`, + 'User-Agent': 'GrubHub/2025.40 (iPhone; iOS 26.0.1; Scale/3.00)', + }; + + const response = await fetch(this.API_URL, { headers }); + + if (!response.ok) { + throw Error(`Failed to fetch restaurant data: ${response.statusText}`); + } + + const data = await response.json(); + + // 2. Log the stored data (JSON.stringify makes it easier to read) + console.log("DEBUG DATA:", JSON.stringify(data, null, 2)); + + // 3. Return the stored data + return data as IGrubhubData; + } + + private parseAndBuildLinks(apiData: IGrubhubData): Record { + const links: Record = {}; + const content = apiData.object?.data?.content || []; + const urlPrefix = "https://www.grubhub.com/restaurant/"; + + for (const item of content) { + const entity = item.entity; + if (!entity) continue; + + const keyId = entity.restaurant_id; + + const nameSlug = this.slugify(entity.name); + const addrSlug = this.slugify(entity.address.street_address); + const localitySlug = this.slugify(entity.address.address_locality); + + const fullLink = `${urlPrefix}${nameSlug}-${addrSlug}-${localitySlug}/${entity.restaurant_id}`; + + links[keyId] = fullLink; + } + + return links; + } + + /** + * A simple utility to convert a string into a URL-friendly slug. + * @param text - The string to slugify. + * @returns A slugified string. + */ + private slugify(text: string): string { + return text.trim().toLowerCase().replace(/\s+/g, '-'); + } +} diff --git a/src/containers/locationBuilder.ts b/src/containers/locationBuilder.ts index a888a94..c0e92b8 100644 --- a/src/containers/locationBuilder.ts +++ b/src/containers/locationBuilder.ts @@ -21,6 +21,7 @@ export default class LocationBuilder { private name: string | undefined; private shortDescription: string | undefined; private description: string | undefined; + private grubhubUrl: string | undefined; private url: string | undefined; private location: string | undefined; private menu: string | undefined; @@ -41,6 +42,12 @@ export default class LocationBuilder { this.shortDescription = load(card)("div.description").text().trim(); } + + setGrubhubUrl(grubhubUrls: Record) { + if (this.conceptId && grubhubUrls[this.conceptId] !== undefined) { + this.grubhubUrl = grubhubUrls[this.conceptId]; + } + } setSoup(soupList: Record) { if ( this.conceptId !== undefined && @@ -127,6 +134,7 @@ export default class LocationBuilder { shortDescription: this.shortDescription, description: this.description, url: this.url, + grubhubUrl: this.grubhubUrl, location: this.location, menu: this.menu, coordinates: this.coordinates, diff --git a/src/db/schema.ts b/src/db/schema.ts index 1863f01..eb72093 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -44,6 +44,7 @@ export const locationDataTable = pgTable("location_data", { menu: text("menu"), /** The human-readable version of the location */ location: text("location").notNull(), + grubhubUrl: text("grubhub_url"), coordinateLat: decimal("coordinate_lat", { mode: "number", scale: 30 }), coordinateLng: decimal("coordinate_lng", { mode: "number", scale: 30 }), acceptsOnlineOrders: boolean("accepts_online_orders").notNull(), @@ -78,6 +79,7 @@ export const overwritesTable = pgTable("overwrites_table", { url: text("url"), menu: text("menu"), location: text("location"), + grubhubUrl: text("grubhub_url"), coordinateLat: decimal("coordinate_lat", { mode: "number", scale: 30 }), coordinateLng: decimal("coordinate_lng", { mode: "number", scale: 30 }), acceptsOnlineOrders: boolean("accepts_online_orders"), @@ -250,3 +252,8 @@ export const reportsTable = pgTable( message: text("message").notNull(), } ) + +export const configTable = pgTable("config", { + key: text("key").primaryKey(), + value: text("value").notNull(), +}); diff --git a/src/db/updateLocation.ts b/src/db/updateLocation.ts index 3fdeeb5..9225d11 100644 --- a/src/db/updateLocation.ts +++ b/src/db/updateLocation.ts @@ -37,6 +37,7 @@ export async function addLocationDataToDb(db: DBType, location: ILocation) { url: location.url, menu: location.menu, location: location.location, + grubhubUrl: location.grubhubUrl, coordinateLat: location.coordinates?.lat, coordinateLng: location.coordinates?.lng, acceptsOnlineOrders: location.acceptsOnlineOrders, diff --git a/src/endpoints/schemas.ts b/src/endpoints/schemas.ts index b884695..19bd9a9 100644 --- a/src/endpoints/schemas.ts +++ b/src/endpoints/schemas.ts @@ -30,6 +30,7 @@ export const LocationSchema = t.Object({ description: t.String(), url: t.String(), menu: t.Nullable(t.String()), + grubhubUrl: t.Nullable(t.String()), coordinateLat: t.Nullable(t.Number()), coordinateLng: t.Nullable(t.Number()), acceptsOnlineOrders: t.Boolean(), diff --git a/src/parser/diningParser.ts b/src/parser/diningParser.ts index 44c847d..3c60373 100644 --- a/src/parser/diningParser.ts +++ b/src/parser/diningParser.ts @@ -1,9 +1,11 @@ import { getHTMLResponse } from "../utils/requestUtils"; import { load } from "cheerio"; import LocationBuilder from "../containers/locationBuilder"; +import GrubhubUrlBuilder from "containers/grubhubUrlBuilder"; import { retrieveSpecials } from "../containers/specials/specialsBuilder"; import { ILocation, ISpecial } from "types"; import { notifySlack } from "utils/slack"; +import { DBType } from "db/db"; /** * Retrieves the HTML from the CMU Dining website and parses the information @@ -17,11 +19,18 @@ export default class DiningParser { static readonly DINING_SOUPS_URL = "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Soups"; + grubhubUrlBuilder: GrubhubUrlBuilder; + + constructor(db: DBType) { + this.grubhubUrlBuilder = new GrubhubUrlBuilder(db); + } + async process(): Promise { const locationBuilders = await this.initializeLocationBuildersFromMainPage(); const [specials, soups] = await this.fetchSpecials(); + const grubhubUrls = await this.grubhubUrlBuilder.build(); for (const builder of locationBuilders) { await builder @@ -35,6 +44,7 @@ export default class DiningParser { ); builder.setSoup(soups); builder.setSpecials(specials); + builder.setGrubhubUrl(grubhubUrls); } return locationBuilders diff --git a/src/reload.ts b/src/reload.ts index 5ec8a63..6837c62 100644 --- a/src/reload.ts +++ b/src/reload.ts @@ -12,7 +12,7 @@ let cachedLocations: ILocation[] = []; export async function refreshDB(db: DBType): Promise { const now = new Date(); console.log(`Reloading Dining API: ${now}`); - const parser = new DiningParser(); + const parser = new DiningParser(db); const locationMerger = new ScrapeResultMerger(); for (let i = 0; i < env.NUMBER_OF_SCRAPES; i++) { diff --git a/src/types.ts b/src/types.ts index 03e48de..4e42677 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,7 @@ export interface ILocation { shortDescription: string | undefined; description: string; url: string; + grubhubUrl: string | undefined; menu: string | undefined; location: string; coordinates: ICoordinate | undefined; @@ -22,6 +23,30 @@ export interface ILocation { todaysSpecials: ISpecial[] | undefined; todaysSoups: ISpecial[] | undefined; } +export interface IGrubhubData { + object: { + data: { + content: { + entity: { + name: string; + address: { + street_address: string; + address_locality: string; + }; + restaurant_id: string; + }; + }[]; + }; + }; +} + +export interface IGrubhubAuthResponse { + session_handle: { + access_token: string; + refresh_token: string; + }; +} + export interface ISpecial { title: string; description: string; @@ -44,6 +69,10 @@ export interface ILocationCoordinateOverwrites { [conceptId: string]: ICoordinate; } +export interface IGrubhubLinkIds { + [conceptId: string]: string; +} + export enum MonthOfTheYear { JANUARY = 1, FEBRUARY = 2, diff --git a/src/utils/requestUtils.ts b/src/utils/requestUtils.ts index 2d44fd5..d29d113 100644 --- a/src/utils/requestUtils.ts +++ b/src/utils/requestUtils.ts @@ -13,14 +13,14 @@ const wait = (ms: number) => { */ export async function getHTMLResponse(url: URL, retriesLeft = 4) { try { - if (!env.IN_TEST_MODE) console.log(`Scraping ${url}`); + // if (!env.IN_TEST_MODE) console.log(`Scraping ${url}`); const response = await axios.get(url.toString()); - if (!env.IN_TEST_MODE) - console.log({ - message: `Scraped ${url}`, - html: response.data, - url: url.toString(), - }); + // if (!env.IN_TEST_MODE) + // console.log({ + // message: `Scraped ${url}`, + // html: response.data, + // url: url.toString(), + // }); const attemptedParsedDate = DateTime.fromRFC2822( response.headers.date diff --git a/tests/expectedData.ts b/tests/expectedData.ts index ae333a3..75d9375 100644 --- a/tests/expectedData.ts +++ b/tests/expectedData.ts @@ -10,6 +10,7 @@ export const expectedLocationData: ILocation[] = [ "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all! For nutritional information about Au Bon Pain's menu items, please click here https://www.aubonpain.com/nutrition To place an Au Bon Pain catering order, please contact 1-800-765-4227 or visit http://aubonpain.com/cateringFor on-campus assistance, call 412-621-1934.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/113", location: "Cohon Center, Second floor", + grubhubUrl: undefined, menu: "https://web.archive.org/web/20230806004812/https://apps.studentaffairs.cmu.edu/dining/dashboard_images/Production/menus/113/abp-menu6.pdf", coordinates: { lat: 40.44, @@ -82,6 +83,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/184", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -106,6 +108,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/95", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -129,6 +132,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/134", + grubhubUrl: "https://www.grubhub.com/restaurant/e.a.t.-(evenings-at-tepper)---rohr-commons-4765-forbes-avenue-pittsburgh/-9070241", location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -152,6 +156,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/178", + grubhubUrl: "https://www.grubhub.com/restaurant/the-edge-5125-margaret-morrison-st-pittsburgh/-7831768", location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -176,6 +181,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/88", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -200,6 +206,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/103", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -224,6 +231,7 @@ export const expectedLocationData: ILocation[] = [ description: "The Exchange offers custom deli sandwiches, soups, hot entrées, fresh baked goods, fruit, yogurt parfaits, snack and energy bars, and other grab-and-go items. The designated coffee bar includes hot brewed La Prima coffee, specialty and organic teas, cold beverages, and bottled juices.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/92", + grubhubUrl: "https://www.grubhub.com/restaurant/the-exchange-4980-margaret-morrison-st-pittsburgh/-5980830", location: "Posner Hall, 1st Floor", menu: "https://web.archive.org/web/20240721001349/https://apps.studentaffairs.cmu.edu/dining/dashboard_images/Production/menus/92/menu-exchange-2024-25-v2.pdf", coordinates: { @@ -298,6 +306,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/126", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -322,6 +331,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/173", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -346,6 +356,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/91", + grubhubUrl: "https://www.grubhub.com/restaurant/el-gallo-de-oro---meal-block-5032-forbes-avenue-pittsburgh/-6013802", location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -370,6 +381,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/139", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -393,6 +405,7 @@ export const expectedLocationData: ILocation[] = [ description: "Authentic Chinese cuisine, featuring sauces made with bone broth, choose your base of noodles or rice and build your own meal with General Tso’s chicken, stir fry tofu, seasonal vegetables and pork ribs in black bean sauce. Braised fish, spring rolls, pork dumplings, red bean rice cakes are available daily. Enjoy fruit smoothies and build-your-own milk or fruit boba tea, with bubble toppings like tapioca, rainbow jelly, lychee jelly and popping boba.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/110", + grubhubUrl: "https://www.grubhub.com/restaurant/hunan-express-5000-forbes-ave-pittsburgh/-6448259", location: "Newell-Simon Atrium", menu: "https://web.archive.org/web/20240901003526/https://apps.studentaffairs.cmu.edu/dining/dashboard_images/Production/menus/110/Fall Menus 2024 (25).pdf", coordinates: { @@ -460,6 +473,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/115", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -484,6 +498,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/136", + grubhubUrl: "https://www.grubhub.com/restaurant/millie's-coffee-'n'-creamery-4765-forbes-avenue-pittsburgh/-8950322", location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -508,6 +523,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/127", + grubhubUrl: "https://www.grubhub.com/restaurant/nourish-(allergen-friendly-kitchen)-5032-forbes-avenue-pittsburgh/-5980773", location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -532,6 +548,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/94", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -556,6 +573,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/186", + grubhubUrl: "https://www.grubhub.com/restaurant/redhawk-coffee-4805-frew-st.-pittsburgh/-11345145", location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -580,6 +598,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/174", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -603,6 +622,7 @@ export const expectedLocationData: ILocation[] = [ description: "Schatz Dining Room is piloting all-you-care-to-eat lunchtime service for all community members, including undergraduate students.Schatz Dining Room offers campus's only all-you-care-to-eat residential dining room. Schatz serves breakfast, lunch and dinner, Monday - Friday, and brunch and dinner on the weekends. Breakfast and brunch offerings include cereal, fruit, egg dishes, breakfast meats, hot griddled items like pancakes and french toast, make-your-own waffles, and an avoiding gluten breakfast station and avoiding gluten options for lunch and dinner. Lunch and dinner include five stations serving a rotating menu of hot entrees, vegan and vegetarian options, soup and salad, made-to-order sandwiches, beverages and desserts. To make a reservation, please email schatzreservations@andrew.cmu.edu", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/108", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -761,6 +781,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/180", + grubhubUrl: "https://www.grubhub.com/restaurant/scotty's-market-5000-forbes-ave,-pittsburgh/-15379708", location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -785,6 +806,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/190", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -809,6 +831,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/188", + grubhubUrl: "https://www.grubhub.com/restaurant/stack'd-underground-morewood-gardens-pittsburgh/-12461575", location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -833,6 +856,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/148", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -856,6 +880,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/82", + grubhubUrl: "https://www.grubhub.com/restaurant/tahini-5125-margaret-morrison-st.-pittsburgh/-6012140", location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -881,6 +906,7 @@ export const expectedLocationData: ILocation[] = [ url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/168", location: "Posner Hall, 1st Floor", menu: "https://web.archive.org/web/20240721001349/https://apps.studentaffairs.cmu.edu/dining/dashboard_images/Production/menus/92/menu-exchange-2024-25-v2.pdf", + grubhubUrl: "https://www.grubhub.com/restaurant/tartan-express-5000-forbes-ave-pittsburgh/-24807638", coordinates: { lat: 401.441354, lng: -791.942125, @@ -938,6 +964,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/114", + grubhubUrl: "https://www.grubhub.com/restaurant/taste-of-india-5125-margaret-morrison-st.-pittsburgh/-6012713", location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -962,6 +989,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/185", + grubhubUrl: "https://www.grubhub.com/restaurant/tepper-taqueria-4765-forbes-ave-pittsburgh/-6479803", location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -986,6 +1014,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/138", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -1009,6 +1038,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/98", + grubhubUrl: undefined, location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -1033,6 +1063,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/155", + grubhubUrl: "https://www.grubhub.com/restaurant/wild-blue-sushi-346-hamerschlag-dr-pittsburgh/-6010765", location: "Cohon Center, Second floor", menu: undefined, coordinates: { @@ -1057,6 +1088,7 @@ export const expectedLocationData: ILocation[] = [ description: "At Au Bon Pain café bakery, each signature recipe is uniquely crafted. You can enjoy delicious hot or iced coffee and teas, espresso drinks, a variety of cold beverages, soup, a customized made-to-order breakfast or lunch sandwich or salad, or you can grab a pre-made salad, sandwich, wrap, yogurt parfait, fresh fruit or snack. There is always something new to try ... healthy choices, comfort food, indulgent treats … try them all!\n \n \n Nutritional information can be found at aubonpain.com/nutrition\n .\n \n \n \n To place a catering order online, visit catering.aubonpain.com\n . To pay with a department or organization’s oracle string, please email your order to abpcmu@grcafes.com\n . For on-campus assistance, call 412-268-1054.", url: "https://apps.studentaffairs.cmu.edu/dining/conceptinfo/Concept/84", + grubhubUrl: "https://www.grubhub.com/restaurant/new-york-bagel-bar-at-zebra-lounge-cfa-pittsburgh/-6013780", location: "Cohon Center, Second floor", menu: undefined, coordinates: { diff --git a/tests/integration.test.ts b/tests/integration.test.ts index 741d256..80e1b2c 100644 --- a/tests/integration.test.ts +++ b/tests/integration.test.ts @@ -16,6 +16,13 @@ import { import { DateTime } from "luxon"; vi.mock("axios"); +vi.mock("containers/grubhubUrlBuilder", () => ({ + default: class { + async build() { + return {}; + } + }, +})); test("the whole thing", async () => { mockAxiosGETMethodWithFilePaths({ @@ -32,7 +39,7 @@ test("the whole thing", async () => { day: 5, }) as DateTime, }); - const parser = new DiningParser(); + const parser = new DiningParser(null as any); const parsedLocationData = await parser.process(); expect(parsedLocationData).toStrictEqual(expectedLocationData); @@ -52,7 +59,7 @@ test("the whole thing, with per-page errors", async () => { day: 5, }) as DateTime, }); - const parser = new DiningParser(); + const parser = new DiningParser(null as any); const parsedLocationData = await parser.process(); expect(parsedLocationData).toStrictEqual(expectedLocationData2); }); @@ -71,7 +78,7 @@ test("specials for The Exchange", async () => { day: 5, }) as DateTime, }); - const parser = new DiningParser(); + const parser = new DiningParser(null as any); expect((await parser.process()).map((data) => data.todaysSpecials)).toEqual( expect.arrayContaining([ [ @@ -103,7 +110,7 @@ test( day: 5, }) as DateTime, }); - const parser = new DiningParser(); + const parser = new DiningParser(null as any); await expect(async () => { await parser.process(); diff --git a/tests/mockTimings.ts b/tests/mockTimings.ts index 87cf2b2..766bc25 100644 --- a/tests/mockTimings.ts +++ b/tests/mockTimings.ts @@ -27,7 +27,7 @@ export async function queryParserAndAssertTimingsCorrect( times: [number, number, number, number, number][], rootDay: DateTime = DateTime.fromObject({ year: 2024, month: 9, day: 9 }) // is the default for most of the tests ) { - const result = await new DiningParser().process(); + const result = await new DiningParser(null as any).process(); expect(result.length).toBe(1); expect(result[0]!.times.sort(_sort)).toStrictEqual( times