diff --git a/drizzle/0008_ordinary_rage.sql b/drizzle/0008_ordinary_rage.sql new file mode 100644 index 0000000..59a1987 --- /dev/null +++ b/drizzle/0008_ordinary_rage.sql @@ -0,0 +1,9 @@ +CREATE TABLE "weekly_time_overwrites_table" ( + "location_id" text NOT NULL, + "weekday" integer NOT NULL, + "time_string" text NOT NULL, + CONSTRAINT "weekday_check" CHECK ("weekly_time_overwrites_table"."weekday" >= 0 AND "weekly_time_overwrites_table"."weekday" < 7) +); +--> statement-breakpoint +ALTER TABLE "weekly_time_overwrites_table" ADD CONSTRAINT "weekly_time_overwrites_table_location_id_location_data_id_fk" FOREIGN KEY ("location_id") REFERENCES "public"."location_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "location_weekday" ON "weekly_time_overwrites_table" USING btree ("location_id","weekday"); \ No newline at end of file diff --git a/drizzle/0009_cooing_snowbird.sql b/drizzle/0009_cooing_snowbird.sql new file mode 100644 index 0000000..d3f42f1 --- /dev/null +++ b/drizzle/0009_cooing_snowbird.sql @@ -0,0 +1,2 @@ +DROP INDEX "location_weekday";--> statement-breakpoint +ALTER TABLE "weekly_time_overwrites_table" ADD CONSTRAINT "weekly_time_overwrites_table_location_id_weekday_pk" PRIMARY KEY("location_id","weekday"); \ No newline at end of file diff --git a/drizzle/meta/0008_snapshot.json b/drizzle/meta/0008_snapshot.json new file mode 100644 index 0000000..0915b8b --- /dev/null +++ b/drizzle/meta/0008_snapshot.json @@ -0,0 +1,1047 @@ +{ + "id": "64e406aa-631a-4384-92f7-0179c3e376d1", + "prevId": "cc713b35-b4cf-4385-ae80-7e5143ae7b3f", + "version": "7", + "dialect": "postgresql", + "tables": { + "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 + }, + "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 + }, + "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.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": { + "location_weekday": { + "name": "location_weekday", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weekday", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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": {}, + "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/0009_snapshot.json b/drizzle/meta/0009_snapshot.json new file mode 100644 index 0000000..c28873b --- /dev/null +++ b/drizzle/meta/0009_snapshot.json @@ -0,0 +1,1033 @@ +{ + "id": "99fa16e2-e656-421e-aad6-02437afea8ce", + "prevId": "64e406aa-631a-4384-92f7-0179c3e376d1", + "version": "7", + "dialect": "postgresql", + "tables": { + "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 + }, + "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 + }, + "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.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 d244424..1d6beab 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -57,6 +57,20 @@ "when": 1768158573717, "tag": "0007_naive_lady_mastermind", "breakpoints": true + }, + { + "idx": 8, + "version": "7", + "when": 1769645286049, + "tag": "0008_ordinary_rage", + "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1769646373776, + "tag": "0009_cooing_snowbird", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/dbQueryUtils.ts b/src/db/dbQueryUtils.ts index 0b62c2e..9315a26 100644 --- a/src/db/dbQueryUtils.ts +++ b/src/db/dbQueryUtils.ts @@ -6,6 +6,7 @@ import { specialsTable, timeOverwritesTable, timesTable, + weeklyTimeOverwritesTable, } from "./schema"; import { DBType } from "./db"; @@ -72,21 +73,21 @@ export class QueryUtils { timesTable, and( eq(locationDataTable.id, timesTable.locationId), - gte(timesTable.date, timeSearchCutoffStr) - ) + gte(timesTable.date, timeSearchCutoffStr), + ), ) .leftJoin( externalIdToInternalIdTable, - eq(externalIdToInternalIdTable.internalId, locationDataTable.id) + eq(externalIdToInternalIdTable.internalId, locationDataTable.id), ); return locationData.reduce< Record< string, - | typeof locationDataTable.$inferSelect & { - times: IDateTimeRange[]; - conceptId: string | null; - } + typeof locationDataTable.$inferSelect & { + times: IDateTimeRange[]; + conceptId: string | null; + } > >((acc, { location_data, location_times, external_id_to_internal_id }) => { if (!acc[location_data.id]) { @@ -126,10 +127,10 @@ export class QueryUtils { [overwrite.locationId]: Object.fromEntries( Object.entries(overwrite).filter(([key, v]) => { return key !== "locationId" && v !== null; - }) + }), ) as RequiredProperty, }), - {} + {}, ); } @@ -144,11 +145,11 @@ export class QueryUtils { .where(gte(timeOverwritesTable.date, earliestDate)) .catch((e) => { notifySlack( - ` Failed to fetch time overwrites with error ${e}` + ` Failed to fetch time overwrites with error ${e}`, ); return []; }); - const idToTimeOverrides = timeOverrides.reduce<{ + const idToPointOverrides = timeOverrides.reduce<{ [locationId in string]: { [date in string]: ITimeSlot[] }; }>((acc, override) => { return { @@ -159,7 +160,27 @@ export class QueryUtils { }, }; }, {}); - return idToTimeOverrides; + const weeklyOverrides = await this.db + .select() + .from(weeklyTimeOverwritesTable) + .catch((e) => { + notifySlack( + ` Failed to fetch weekly time overwrites with error ${e}`, + ); + return []; + }); + const idToWeeklyOverrides = weeklyOverrides.reduce<{ + [locationId in string]: { [weekday in number]: ITimeSlot[] }; + }>((acc, curOverride) => { + return { + ...acc, + [curOverride.locationId]: { + ...acc[curOverride.locationId], + [curOverride.weekday]: parseTimeSlots(curOverride.timeString), + }, + }; + }, {}); + return { idToPointOverrides, idToWeeklyOverrides }; } async getEmails(): Promise<{ name: string; email: string }[]> { diff --git a/src/db/getLocations.ts b/src/db/getLocations.ts index e8d8865..f73046b 100644 --- a/src/db/getLocations.ts +++ b/src/db/getLocations.ts @@ -15,11 +15,13 @@ export async function getAllLocationsFromDB(db: DBType, today: DateTime) { const DB = new QueryUtils(db); const locationIdToData = await DB.getLocationIdToDataMap( - timeSearchCutoff.toSQLDate() + timeSearchCutoff.toSQLDate(), ); const specials = await DB.getSpecials(today.toSQLDate()); const generalOverrides = await DB.getGeneralOverrides(); - const timeOverrides = await DB.getTimeOverrides(timeSearchCutoff.toSQLDate()); + const { idToPointOverrides, idToWeeklyOverrides } = await DB.getTimeOverrides( + timeSearchCutoff.toSQLDate(), + ); // apply overrides, merge all time intervals, and add specials const finalLocationData = Object.entries(locationIdToData).map( @@ -28,7 +30,12 @@ export async function getAllLocationsFromDB(db: DBType, today: DateTime) { ...data, ...generalOverrides[id], // this line only works because the override table has the same columns as the normal table times: remapAndMergeTimeIntervals( - applyTimeOverrides(data.times, timeOverrides[id]) + applyTimeOverrides( + data.times, + timeSearchCutoff, + idToPointOverrides[id], + idToWeeklyOverrides[id], + ), ), // .map( // (time) => @@ -39,26 +46,54 @@ export async function getAllLocationsFromDB(db: DBType, today: DateTime) { todaysSoups: specials[id]?.soups ?? [], todaysSpecials: specials[id]?.specials ?? [], }; - } + }, ); return finalLocationData; } function applyTimeOverrides( originalTimes: IDateTimeRange[], - overrideTimes?: { [date in string]: ITimeSlot[] } + startDate: DateTime, + overridePointTimes?: { [date in string]: ITimeSlot[] }, + overrideWeeklyTimes?: { [weekday in number]: ITimeSlot[] }, ) { - if (overrideTimes === undefined) return originalTimes; - for (const [overrideDate, timeRanges] of Object.entries(overrideTimes)) { - originalTimes = originalTimes.filter((time) => time.date !== overrideDate); + if (overridePointTimes === undefined && overrideWeeklyTimes === undefined) + return originalTimes; + if (overrideWeeklyTimes !== undefined) { + for (let i = 0; i < 8; i++) { + // 7 + 1 day prior + const curDate = startDate.plus({ days: i }); + const overrideIntervals = overrideWeeklyTimes[curDate.weekday % 7]; + if (overrideIntervals === undefined) continue; + originalTimes = originalTimes.filter( + (time) => time.date !== curDate.toSQLDate(), + ); + originalTimes.push( + ...overrideIntervals.map((rng) => ({ + date: curDate.toSQLDate(), + startMinutesSinceMidnight: rng.start.hour * 60 + rng.start.minute, + endMinutesSinceMidnight: rng.end.hour * 60 + rng.end.minute, + })), + ); + } + } + //takes precedence over weekly overrides + if (overridePointTimes !== undefined) { + for (const [overrideDate, timeRanges] of Object.entries( + overridePointTimes, + )) { + originalTimes = originalTimes.filter( + (time) => time.date !== overrideDate, + ); - originalTimes.push( - ...timeRanges.map((rng) => ({ - date: overrideDate, - startMinutesSinceMidnight: rng.start.hour * 60 + rng.start.minute, - endMinutesSinceMidnight: rng.end.hour * 60 + rng.end.minute, - })) - ); + originalTimes.push( + ...timeRanges.map((rng) => ({ + date: overrideDate, + startMinutesSinceMidnight: rng.start.hour * 60 + rng.start.minute, + endMinutesSinceMidnight: rng.end.hour * 60 + rng.end.minute, + })), + ); + } } return originalTimes; } diff --git a/src/db/schema.ts b/src/db/schema.ts index e7f63e2..a00067b 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -33,7 +33,7 @@ export const externalIdToInternalIdTable = pgTable( externalId: text("external_id").unique().primaryKey(), type: externalIdType("external_id_type").default("concept_id"), }, - (table) => [index("internal_id").on(table.internalId)] + (table) => [index("internal_id").on(table.internalId)], ); export const locationDataTable = pgTable("location_data", { id: text("id").primaryKey(), @@ -61,7 +61,7 @@ export const timesTable = pgTable( startTime: integer("start_time").notNull(), endTime: integer("end_time").notNull(), }, - (table) => [index("date_lookup").on(table.locationId, table.date)] + (table) => [index("date_lookup").on(table.locationId, table.date)], ); /** * Includes everything in ILocation except for soups and specials @@ -93,7 +93,24 @@ export const timeOverwritesTable = pgTable( date: date("date").notNull(), timeString: text("time_string").notNull(), }, - (table) => [primaryKey({ columns: [table.locationId, table.date] })] + (table) => [primaryKey({ columns: [table.locationId, table.date] })], +); +export const weeklyTimeOverwritesTable = pgTable( + "weekly_time_overwrites_table", + { + locationId: text("location_id") + .notNull() + .references(() => locationDataTable.id, { + onDelete: "cascade", + }), + /** sunday is 0, monday is 1, ... */ + weekday: integer("weekday").notNull(), + timeString: text("time_string").notNull(), + }, + (t) => [ + primaryKey({ columns: [t.locationId, t.weekday] }), + check("weekday_check", sql`${t.weekday} >= 0 AND ${t.weekday} < 7`), // sunday is 0, monday is 1, etc. + ], ); export const specialType = pgEnum("specialType", ["special", "soup"]); @@ -128,7 +145,7 @@ export const userTable = pgTable( mode: "date", }).defaultNow(), }, - (table) => [index("google_id").on(table.googleId)] + (table) => [index("google_id").on(table.googleId)], ); export const userSessionTable = pgTable("sessions", { sessionId: text("id").notNull().primaryKey(), @@ -178,9 +195,9 @@ export const tagReviewTable = pgTable( uniqueIndex("tag_reviews_location_tag_user_uniq").on( t.locationId, t.tagId, - t.userId + t.userId, ), - ] + ], ); export const starReviewTable = pgTable( "star_reviews", @@ -208,7 +225,7 @@ export const starReviewTable = pgTable( uniqueIndex("star_reviews_location_user_uniq").on(t.locationId, t.userId), check( "rating_number_check", - sql`${t.starRating} > 0 AND ${t.starRating} <= 5 AND mod(${t.starRating}*2,1) = 0` + sql`${t.starRating} > 0 AND ${t.starRating} <= 5 AND mod(${t.starRating}*2,1) = 0`, ), // rating is a multiple of .5 - ] + ], ); diff --git a/src/db/updateLocation.ts b/src/db/updateLocation.ts index e2b329f..3fdeeb5 100644 --- a/src/db/updateLocation.ts +++ b/src/db/updateLocation.ts @@ -6,6 +6,7 @@ import { specialsTable, timeOverwritesTable, timesTable, + weeklyTimeOverwritesTable, } from "./schema"; import { and, eq, gte } from "drizzle-orm"; import { pad } from "utils/timeUtils"; @@ -50,7 +51,7 @@ export async function addLocationDataToDb(db: DBType, location: ILocation) { }); const todayAsSQLString = `${location.today.year}-${pad( - location.today.month + location.today.month, )}-${pad(location.today.day)}`; // add specials await tx @@ -58,8 +59,8 @@ export async function addLocationDataToDb(db: DBType, location: ILocation) { .where( and( eq(specialsTable.locationId, internalId), - eq(specialsTable.date, todayAsSQLString) - ) + eq(specialsTable.date, todayAsSQLString), + ), ); const specials = [ ...(location.todaysSpecials?.map((sp) => ({ @@ -79,7 +80,7 @@ export async function addLocationDataToDb(db: DBType, location: ILocation) { name: special.title, description: special.description, type: special.type, - })) + })), ); // remove rows from whenever the scrape started from (aka remove entries corresponding to the last 7 days) @@ -88,8 +89,8 @@ export async function addLocationDataToDb(db: DBType, location: ILocation) { .where( and( eq(timesTable.locationId, internalId), - and(gte(timesTable.date, todayAsSQLString)) - ) + and(gte(timesTable.date, todayAsSQLString)), + ), ); if (location.times.length) { await tx.insert(timesTable).values( @@ -98,7 +99,7 @@ export async function addLocationDataToDb(db: DBType, location: ILocation) { date: `${time.year}-${pad(time.month)}-${pad(time.day)}`, startTime: time.startMinutesFromMidnight, endTime: time.endMinutesFromMidnight, - })) + })), ); } @@ -125,7 +126,7 @@ export async function addTimeOverride( db: DBType, locationId: string, date: string, - timeStringOverride: string + timeStringOverride: string, ) { const parsedDate = DateTime.fromFormat(date, "M/d/yy"); if (!parsedDate.isValid) { @@ -145,3 +146,26 @@ export async function addTimeOverride( }); return true; } +export async function addWeeklyTimeOverride( + db: DBType, + locationId: string, + weekday: number, + timeStringOverride: string, +) { + const rowToInsert: typeof weeklyTimeOverwritesTable.$inferInsert = { + weekday, + locationId: locationId, + timeString: timeStringOverride, + }; + await db + .insert(weeklyTimeOverwritesTable) + .values(rowToInsert) + .onConflictDoUpdate({ + target: [ + weeklyTimeOverwritesTable.locationId, + weeklyTimeOverwritesTable.weekday, + ], + set: rowToInsert, + }); + return true; +} diff --git a/tests/database.test.ts b/tests/database.test.ts index f66d00c..dbce728 100644 --- a/tests/database.test.ts +++ b/tests/database.test.ts @@ -1,4 +1,8 @@ -import { addLocationDataToDb, addTimeOverride } from "db/updateLocation"; +import { + addLocationDataToDb, + addTimeOverride, + addWeeklyTimeOverride, +} from "db/updateLocation"; import { getAllLocationsFromDB } from "db/getLocations"; import { DateTime } from "luxon"; import { ILocation } from "types"; @@ -54,7 +58,7 @@ describe("general location insertion tests", () => { "properly resets state on every new dbTest", async ({ ctx: { db } }) => { expect(await getAllLocationsFromDB(db, DateTime.now())).toEqual([]); - } + }, ); dbTest.concurrent( "works on insertion with times", @@ -94,7 +98,7 @@ describe("general location insertion tests", () => { ], }, ]); - } + }, ); dbTest.concurrent( "works on insertion with times (tests search window)", @@ -114,7 +118,7 @@ describe("general location insertion tests", () => { }); const dbResult = await getAllLocationsFromDB( db, - parseDate("1/3/25") // 2 days after latest time + parseDate("1/3/25"), // 2 days after latest time ); expect(dbResult).toEqual([ { @@ -124,7 +128,7 @@ describe("general location insertion tests", () => { times: [], }, ]); - } + }, ); dbTest.concurrent( "works on insertion with times (tests search window)", @@ -143,7 +147,7 @@ describe("general location insertion tests", () => { }); const dbResult = await getAllLocationsFromDB( db, - parseDate("1/3/25") // 2 days after latest time + parseDate("1/3/25"), // 2 days after latest time ); expect(dbResult).toEqual([ { @@ -157,7 +161,7 @@ describe("general location insertion tests", () => { ], }, ]); - } + }, ); dbTest.concurrent( "works on insertion with times (DST - start 2AM -> 3AM) (3/9/25)", @@ -179,7 +183,7 @@ describe("general location insertion tests", () => { ], }, ]); - } + }, ); dbTest.concurrent( "works on insertion with times (DST - start 2AM -> 3AM) (3/9/25)", @@ -201,7 +205,29 @@ describe("general location insertion tests", () => { ], }, ]); - } + }, + ); + dbTest.concurrent( + "works on insertion with times (DST - start 2AM -> 3AM) (3/9/25)", + async ({ ctx: { db } }) => { + const id = await addLocationDataToDb(db, { + ...locationIn, + times: [parseTime("3/8/25", "2:30 am", "2:15 am")], // end time is earlier than start, so it rolls to 3/9 2:15 AM, which is skipped to 3:15 AM by DST jump + }); + const dbResult = await getAllLocationsFromDB(db, parseDate("1/3/25")); + expect(dbResult).toEqual([ + { + ...locationOut, + id: id, + times: [ + { + start: timeToUnixTimestamp("3/8/25 2:30 AM"), + end: timeToUnixTimestamp("3/9/25 3:15 AM"), + }, + ], + }, + ]); + }, ); dbTest.concurrent( "works on insertion with times (DST - end 2AM -> 1AM) (11/2/25) [if a place closes at 2 AM, we assume it's the second 2AM (I mean, the first 2AM technically doesn't exist...)", @@ -228,7 +254,7 @@ describe("general location insertion tests", () => { ], }, ]); - } + }, ); dbTest.concurrent( "works on insertion with times (DST - end 2AM -> 1AM) (11/2/25) [if a place closes at 1:30 AM, we assume dbTest's the first 1:30 AM and not the second]", @@ -255,7 +281,7 @@ describe("general location insertion tests", () => { ], }, ]); - } + }, ); dbTest.concurrent("works on specials", async ({ ctx: { db } }) => { const id = await addLocationDataToDb(db, { @@ -386,7 +412,7 @@ describe("general location insertion tests", () => { ], }, ]); - } + }, ); dbTest.concurrent("works on time overwrites", async ({ ctx: { db } }) => { const id = await addLocationDataToDb(db, { @@ -403,13 +429,13 @@ describe("general location insertion tests", () => { db, id, "1/1/25", - "2:00 AM - 3:00 PM" + "2:00 AM - 3:00 PM", ); const success2 = await addTimeOverride( db, id, "1/1/25", - "3:00 AM - 4:00 PM" + "3:00 AM - 4:00 PM", ); // second one should overwrite the first one expect(success1).toBe(true); expect(success2).toBe(true); @@ -482,6 +508,101 @@ describe("general location insertion tests", () => { }, ]); }); + dbTest.concurrent( + "point time overwrites take precedence over weekly time overwrites", + async ({ ctx: { db } }) => { + const locationId = await addLocationDataToDb(db, { + ...locationIn, + times: [ + parseTime("1/1/25", "5:00 am", "5:00 pm"), + parseTime("1/2/25", "5:00 am", "5:00 pm"), + parseTime("1/3/25", "5:00 am", "5:00 pm"), + parseTime("1/4/25", "5:00 am", "5:00 pm"), + parseTime("1/5/25", "5:00 am", "5:00 pm"), + ], + }); + + const success = await addTimeOverride( + db, + locationId, + "1/1/25", + "5:00 AM - 6:00 AM", + ); + await addWeeklyTimeOverride(db, locationId, 3, "9:00 PM - 10:00 PM"); + await addWeeklyTimeOverride(db, locationId, 3, "9:00 PM - 11:00 PM"); + await addWeeklyTimeOverride(db, locationId, 4, "9:00 PM - 11:00 PM"); + expect(success).toBe(true); + expect(await getAllLocationsFromDB(db, parseDate("1/1/25"))).toEqual([ + { + ...locationOut, + id: locationId, + times: [ + { + start: timeToUnixTimestamp("1/1/25 5:00 AM"), + end: timeToUnixTimestamp("1/1/25 6:00 AM"), + }, + { + start: timeToUnixTimestamp("1/2/25 9:00 PM"), + end: timeToUnixTimestamp("1/2/25 11:00 PM"), + }, + { + start: timeToUnixTimestamp("1/3/25 5:00 AM"), + end: timeToUnixTimestamp("1/3/25 5:00 PM"), + }, + { + start: timeToUnixTimestamp("1/4/25 5:00 AM"), + end: timeToUnixTimestamp("1/4/25 5:00 PM"), + }, + { + start: timeToUnixTimestamp("1/5/25 5:00 AM"), + end: timeToUnixTimestamp("1/5/25 5:00 PM"), + }, + ], + }, + ]); + }, + ); + dbTest.concurrent("weekly time overwrites", async ({ ctx: { db } }) => { + const locationId = await addLocationDataToDb(db, { + ...locationIn, + times: [ + parseTime("1/1/25", "5:00 am", "5:00 pm"), + parseTime("1/2/25", "5:00 am", "5:00 pm"), + parseTime("1/3/25", "5:00 am", "5:00 pm"), + parseTime("1/4/25", "5:00 am", "5:00 pm"), + parseTime("1/5/25", "5:00 am", "5:00 pm"), + ], + }); + + await addWeeklyTimeOverride(db, locationId, 3, "9:00 PM - 10:00 PM"); + await addWeeklyTimeOverride(db, locationId, 3, "9:00 PM - 11:00 PM"); + await addWeeklyTimeOverride(db, locationId, 4, "9:00 PM - 11:00 PM"); + await addWeeklyTimeOverride(db, locationId, 5, "CLOSED"); + expect(await getAllLocationsFromDB(db, parseDate("1/1/25"))).toEqual([ + { + ...locationOut, + id: locationId, + times: [ + { + start: timeToUnixTimestamp("1/1/25 9:00 PM"), + end: timeToUnixTimestamp("1/1/25 11:00 PM"), + }, + { + start: timeToUnixTimestamp("1/2/25 9:00 PM"), + end: timeToUnixTimestamp("1/2/25 11:00 PM"), + }, + { + start: timeToUnixTimestamp("1/4/25 5:00 AM"), + end: timeToUnixTimestamp("1/4/25 5:00 PM"), + }, + { + start: timeToUnixTimestamp("1/5/25 5:00 AM"), + end: timeToUnixTimestamp("1/5/25 5:00 PM"), + }, + ], + }, + ]); + }); dbTest.concurrent("works on general overwrites", async ({ ctx: { db } }) => { const id = await addLocationDataToDb(db, { ...locationIn, @@ -526,7 +647,7 @@ describe("general location insertion tests", () => { id: id2, conceptId: "2", }, - ]) + ]), ); }); dbTest.concurrent( @@ -571,7 +692,7 @@ describe("general location insertion tests", () => { ], }, ]); - } + }, ); dbTest.concurrent("time merging", async ({ ctx: { db } }) => { const id = await addLocationDataToDb(db, {