From de17752d0ba88c5eec93464d83d03e6c0ef593cc Mon Sep 17 00:00:00 2001 From: stanleymw <107821509+stanleymw@users.noreply.github.com> Date: Sat, 17 Jan 2026 18:04:16 -0500 Subject: [PATCH 01/20] add report error endpoint --- src/db/schema.ts | 21 +++++++++++++++++++++ src/endpoints/misc.ts | 32 ++++++++++++++++++++++++++++++++ src/env.ts | 3 ++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/db/schema.ts b/src/db/schema.ts index e7f63e2..3991c38 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -212,3 +212,24 @@ export const starReviewTable = pgTable( ), // rating is a multiple of .5 ] ); + +export const reportsTable = pgTable( + "reports", + { + id: integer("id").generatedAlwaysAsIdentity().primaryKey(), + userId: integer("user_id") + .references(() => userTable.id, { + onDelete: "cascade", + }), + createdAt: timestamp("created_at", { + withTimezone: true, + mode: "date", + }).defaultNow(), + locationId: text("location_id") + .references(() => locationDataTable.id, { + onDelete: "cascade", + }) + .notNull(), + message: text("message").notNull(), + } +) diff --git a/src/endpoints/misc.ts b/src/endpoints/misc.ts index fe0399f..4a5c628 100644 --- a/src/endpoints/misc.ts +++ b/src/endpoints/misc.ts @@ -6,6 +6,8 @@ import { DateTime } from "luxon"; import { notifySlack } from "utils/slack"; import { LocationsSchema } from "./schemas"; import { env } from "env"; +import { reportsTable } from "db/schema"; +import { fetchUserDetails } from "./auth"; export const miscEndpoints = new Elysia(); miscEndpoints.get( @@ -51,3 +53,33 @@ miscEndpoints.post( detail: { hide: true }, } ); + +miscEndpoints.post( + "/reportError", + async ({cookie, body: { location_id, message } }) => { + const session = cookie["session_id"]!.value as string | undefined; + const userDetails = await fetchUserDetails(session); + + const userId = userDetails?.id; + + await notifySlack(` + User (${userId}) has reported an error with location ${location_id}: + ${message} + `, env.SLACK_MAIN_CHANNEL_WEBHOOK_URL); + db.insert(reportsTable).values({ + locationId: location_id, + message: message, + userId: userId, + }) + }, + { + body: t.Object({ + location_id: t.String(), + message: t.String(), + }), + detail: { + description: + "Endpoint for reporting errors in information", + }, + } +); diff --git a/src/env.ts b/src/env.ts index 550e162..6153ec0 100644 --- a/src/env.ts +++ b/src/env.ts @@ -7,6 +7,7 @@ const envSchema = z.object({ RELOAD_WAIT_INTERVAL: z.coerce.number().default(1000 * 60 * 30), // 30 min SLACK_BACKEND_WEBHOOK_URL: z.string(), SLACK_FRONTEND_WEBHOOK_URL: z.string(), + SLACK_MAIN_CHANNEL_WEBHOOK_URL: z.string(), AXIOS_RETRY_INTERVAL_MS: z.coerce.number().default(1000), /** Special flag when running automated tests */ IN_TEST_MODE: z.stringbool().default(false), @@ -20,7 +21,7 @@ const envSchema = z.object({ .transform((x) => x === "true") .default(false), ENV: z.enum(["dev", "staging", "prod"]), - SESSION_COOKIE_SIGNING_SECRET: z.string(), + SESSION_COOKIE_SIGNING_SECRET: z.string().default("lemon melon cookie"), HARDCODE_SESSION_FOR_DEV_TESTING: z.string().optional(), }); console.log(envSchema.parse(process.env)); From 11c2c7a343306ed2d552404dc164b4fa12a7847c Mon Sep 17 00:00:00 2001 From: Aromia Date: Sat, 31 Jan 2026 17:32:04 -0500 Subject: [PATCH 02/20] feat: add ratingsAvg field for endpoint locations --- README.md | 10 +++++++++- src/db/dbQueryUtils.ts | 22 ++++++++++++++++++---- src/db/getLocations.ts | 2 ++ src/endpoints/schemas.ts | 1 + 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2b40bbf..561b693 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,15 @@ Now install the API's dependencies by 'cd'-ing into the root of the repository a pnpm install ``` -Start your local database with `pnpm db:start`, `pnpm db:push` (if this is your first time) and then start the server with `pnpm dev` and it should work, assuming you have the correct env variables. (To see the contents of the database, I recommend using DBeaver. You can also run `pnpm db:studio` to start up drizzle studio) +Then start your local database with (assuming you have the correct env variables) + +```bash +pnpm db:start +pnpm db:push # if this is your first time running the db +pnpm dev +``` + +To see the contents of the database, I recommend using DBeaver. You can also run `pnpm db:studio` to start up drizzle studio ## Database schema changes (important!) diff --git a/src/db/dbQueryUtils.ts b/src/db/dbQueryUtils.ts index 0b62c2e..e476e53 100644 --- a/src/db/dbQueryUtils.ts +++ b/src/db/dbQueryUtils.ts @@ -1,9 +1,11 @@ +import { avg, sql } from "drizzle-orm"; import { externalIdToInternalIdTable, emailTable, locationDataTable, overwritesTable, specialsTable, + starReviewTable, timeOverwritesTable, timesTable, } from "./schema"; @@ -83,10 +85,10 @@ export class QueryUtils { 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]) { @@ -162,6 +164,18 @@ export class QueryUtils { return idToTimeOverrides; } + async getRatingsAvgs() { + const ratingsAvgs = await this.db + .select({ + starRating: sql`cast(${avg(starReviewTable.starRating)} as decimal(2,1))`, + locationId: starReviewTable.locationId, + }) + .from(starReviewTable) + .groupBy(starReviewTable.locationId); + + return Object.fromEntries(ratingsAvgs.map((e) => [e.locationId, e.starRating])); + } + async getEmails(): Promise<{ name: string; email: string }[]> { const result = await this.db .select({ diff --git a/src/db/getLocations.ts b/src/db/getLocations.ts index e8d8865..7702d6b 100644 --- a/src/db/getLocations.ts +++ b/src/db/getLocations.ts @@ -20,6 +20,7 @@ export async function getAllLocationsFromDB(db: DBType, today: DateTime) { const specials = await DB.getSpecials(today.toSQLDate()); const generalOverrides = await DB.getGeneralOverrides(); const timeOverrides = await DB.getTimeOverrides(timeSearchCutoff.toSQLDate()); + const ratingsAvgs = await DB.getRatingsAvgs(); // apply overrides, merge all time intervals, and add specials const finalLocationData = Object.entries(locationIdToData).map( @@ -36,6 +37,7 @@ export async function getAllLocationsFromDB(db: DBType, today: DateTime) { // time.end // ).toLocaleString()}` // ), + ratingsAvg: ratingsAvgs[id] ?? null, todaysSoups: specials[id]?.soups ?? [], todaysSpecials: specials[id]?.specials ?? [], }; diff --git a/src/endpoints/schemas.ts b/src/endpoints/schemas.ts index 6ccf14b..e053e4e 100644 --- a/src/endpoints/schemas.ts +++ b/src/endpoints/schemas.ts @@ -24,6 +24,7 @@ export const LocationSchema = t.Object({ name: t.Nullable(t.String({ examples: ["Schatz"] })), location: t.String(), + ratingsAvg: t.Nullable(t.Number()), shortDescription: t.Nullable(t.String()), description: t.String(), url: t.String(), From 879287e6a72bb1c6e6462b1db7c54ecb6fc9ac4d Mon Sep 17 00:00:00 2001 From: Aromia Date: Sat, 31 Jan 2026 17:50:22 -0500 Subject: [PATCH 03/20] chore: fix typo --- src/endpoints/reviews.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/endpoints/reviews.ts b/src/endpoints/reviews.ts index 7198609..c5b1120 100644 --- a/src/endpoints/reviews.ts +++ b/src/endpoints/reviews.ts @@ -40,7 +40,7 @@ reviewEndpoints buckets: t.Array(t.Number(), { example: [0, 1, 0, 4, 12, 4], description: - "Count of ratings of star rating [{.5},{1,1.5},{2,2.5},{3,3.5},{4,4.5},{5}", + "Count of ratings of star rating [{.5},{1,1.5},{2,2.5},{3,3.5},{4,4.5},{5}]", }), }), tagData: t.Array( From d68120cd93296ec43609f26e8a26b2472e28c6ea Mon Sep 17 00:00:00 2001 From: Aromia Date: Sat, 31 Jan 2026 20:53:50 -0500 Subject: [PATCH 04/20] chore: fix test --- tests/database.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/database.test.ts b/tests/database.test.ts index f66d00c..8763ef8 100644 --- a/tests/database.test.ts +++ b/tests/database.test.ts @@ -30,6 +30,7 @@ const locationIn: ILocation = { const locationOut = { id: "DYNAMICALLY GENERATED, replace with real id", name: "dbTest", + ratingsAvg: null, shortDescription: "hi", description: "description", url: "https://hi.com", From 6a38c2c48d92b259e857213e53cec3442810f00d Mon Sep 17 00:00:00 2001 From: Aromia Date: Sat, 31 Jan 2026 21:25:39 -0500 Subject: [PATCH 05/20] chore: explicite type cast --- src/db/dbQueryUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/db/dbQueryUtils.ts b/src/db/dbQueryUtils.ts index e476e53..40e6995 100644 --- a/src/db/dbQueryUtils.ts +++ b/src/db/dbQueryUtils.ts @@ -167,7 +167,8 @@ export class QueryUtils { async getRatingsAvgs() { const ratingsAvgs = await this.db .select({ - starRating: sql`cast(${avg(starReviewTable.starRating)} as decimal(2,1))`, + // only declares it to be a number, we explicitly cast it to a num + starRating: sql`cast(${avg(starReviewTable.starRating)} as decimal(2,1))`.mapWith(Number), locationId: starReviewTable.locationId, }) .from(starReviewTable) From 88089f923a2c03ad9cab6dc9fa4b5e80f8e1f544 Mon Sep 17 00:00:00 2001 From: Aromia Date: Mon, 9 Feb 2026 16:22:42 -0500 Subject: [PATCH 06/20] feat: add ratingsCount field for endpoint locations --- src/db/dbQueryUtils.ts | 14 +++++++++++++- src/db/getLocations.ts | 2 ++ src/endpoints/schemas.ts | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/db/dbQueryUtils.ts b/src/db/dbQueryUtils.ts index e48e52f..3d9e849 100644 --- a/src/db/dbQueryUtils.ts +++ b/src/db/dbQueryUtils.ts @@ -1,4 +1,4 @@ -import { avg, sql } from "drizzle-orm"; +import { avg, count, sql } from "drizzle-orm"; import { externalIdToInternalIdTable, emailTable, @@ -198,6 +198,18 @@ export class QueryUtils { return Object.fromEntries(ratingsAvgs.map((e) => [e.locationId, e.starRating])); } + async getRatingsCounts() { + const ratingsCounts = await this.db + .select({ + count: count(starReviewTable.id).mapWith(Number), + locationId: starReviewTable.locationId, + }) + .from(starReviewTable) + .groupBy(starReviewTable.locationId); + + return Object.fromEntries(ratingsCounts.map((e) => [e.locationId, e.count])); + } + async getEmails(): Promise<{ name: string; email: string }[]> { const result = await this.db .select({ diff --git a/src/db/getLocations.ts b/src/db/getLocations.ts index 7ea4bff..a7fa0b4 100644 --- a/src/db/getLocations.ts +++ b/src/db/getLocations.ts @@ -23,6 +23,7 @@ export async function getAllLocationsFromDB(db: DBType, today: DateTime) { timeSearchCutoff.toSQLDate(), ); const ratingsAvgs = await DB.getRatingsAvgs(); + const ratingsCounts = await DB.getRatingsCounts(); // apply overrides, merge all time intervals, and add specials const finalLocationData = Object.entries(locationIdToData).map( @@ -45,6 +46,7 @@ export async function getAllLocationsFromDB(db: DBType, today: DateTime) { // ).toLocaleString()}` // ), ratingsAvg: ratingsAvgs[id] ?? null, + ratingsCount: ratingsCounts[id] ?? 0, todaysSoups: specials[id]?.soups ?? [], todaysSpecials: specials[id]?.specials ?? [], }; diff --git a/src/endpoints/schemas.ts b/src/endpoints/schemas.ts index e053e4e..b884695 100644 --- a/src/endpoints/schemas.ts +++ b/src/endpoints/schemas.ts @@ -25,6 +25,7 @@ export const LocationSchema = t.Object({ name: t.Nullable(t.String({ examples: ["Schatz"] })), location: t.String(), ratingsAvg: t.Nullable(t.Number()), + ratingsCount: t.Number(), shortDescription: t.Nullable(t.String()), description: t.String(), url: t.String(), From f946ec861a2e0a017dab24dfda79279a48b19178 Mon Sep 17 00:00:00 2001 From: Aromia Date: Mon, 9 Feb 2026 16:41:50 -0500 Subject: [PATCH 07/20] chore: fix test --- tests/database.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/database.test.ts b/tests/database.test.ts index 78a5ef1..99e7868 100644 --- a/tests/database.test.ts +++ b/tests/database.test.ts @@ -35,6 +35,7 @@ const locationOut = { id: "DYNAMICALLY GENERATED, replace with real id", name: "dbTest", ratingsAvg: null, + ratingsCount: 0, shortDescription: "hi", description: "description", url: "https://hi.com", From 9a903a468a3c069acb342f0b1f2dd1bd897502c3 Mon Sep 17 00:00:00 2001 From: topshenyi-web Date: Tue, 10 Feb 2026 22:11:03 -0500 Subject: [PATCH 08/20] chore: reword email footer --- src/endpoints/misc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/endpoints/misc.ts b/src/endpoints/misc.ts index 74c9fda..11b8408 100644 --- a/src/endpoints/misc.ts +++ b/src/endpoints/misc.ts @@ -81,7 +81,7 @@ async function runBackgroundJobForErrorReport({ env.ALERT_EMAIL_SEND, env.ALERT_EMAIL_CC, `[CMU Eats] Report for ${locationName}`, - `${message}\n\nBest,\nCMU Eats team`, + `${message}\n\nBest,\nCMU Eats automated report system`, ); await notifySlack( `Report for ${locationName} (\`${locationId}\`): ${message} \nEmailed: ${received.join(", ")}`, From ead9e3c6ba556162f2ec6b3854b7963b6bc13483 Mon Sep 17 00:00:00 2001 From: Aromia Date: Sat, 14 Feb 2026 17:10:13 -0500 Subject: [PATCH 09/20] chore: merge db queries --- src/db/dbQueryUtils.ts | 32 ++++++++++++++++---------------- src/db/getLocations.ts | 3 +-- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/db/dbQueryUtils.ts b/src/db/dbQueryUtils.ts index 3d9e849..90ec156 100644 --- a/src/db/dbQueryUtils.ts +++ b/src/db/dbQueryUtils.ts @@ -185,29 +185,29 @@ export class QueryUtils { return { idToPointOverrides, idToWeeklyOverrides }; } - async getRatingsAvgs() { - const ratingsAvgs = await this.db - .select({ - // only declares it to be a number, we explicitly cast it to a num - starRating: sql`cast(${avg(starReviewTable.starRating)} as decimal(2,1))`.mapWith(Number), - locationId: starReviewTable.locationId, - }) - .from(starReviewTable) - .groupBy(starReviewTable.locationId); - - return Object.fromEntries(ratingsAvgs.map((e) => [e.locationId, e.starRating])); - } - - async getRatingsCounts() { - const ratingsCounts = await this.db + async getRatingsAvgsAndCounts(): Promise< + [Record, Record] + > { + const ratings = await this.db .select({ + starRating: + sql`cast(${avg(starReviewTable.starRating)} as decimal(2,1))`.mapWith( + Number, + ), count: count(starReviewTable.id).mapWith(Number), locationId: starReviewTable.locationId, }) .from(starReviewTable) .groupBy(starReviewTable.locationId); - return Object.fromEntries(ratingsCounts.map((e) => [e.locationId, e.count])); + const ratingsAvgs = Object.fromEntries( + ratings.map((e) => [e.locationId, e.starRating]), + ); + const ratingsCounts = Object.fromEntries( + ratings.map((e) => [e.locationId, e.count]), + ); + + return [ratingsAvgs, ratingsCounts]; } async getEmails(): Promise<{ name: string; email: string }[]> { diff --git a/src/db/getLocations.ts b/src/db/getLocations.ts index a7fa0b4..f6d932e 100644 --- a/src/db/getLocations.ts +++ b/src/db/getLocations.ts @@ -22,8 +22,7 @@ export async function getAllLocationsFromDB(db: DBType, today: DateTime) { const { idToPointOverrides, idToWeeklyOverrides } = await DB.getTimeOverrides( timeSearchCutoff.toSQLDate(), ); - const ratingsAvgs = await DB.getRatingsAvgs(); - const ratingsCounts = await DB.getRatingsCounts(); + const [ratingsAvgs, ratingsCounts] = await DB.getRatingsAvgsAndCounts(); // apply overrides, merge all time intervals, and add specials const finalLocationData = Object.entries(locationIdToData).map( From c35a90973539b942b86d96951adeebadfb182f0f Mon Sep 17 00:00:00 2001 From: Aromia Date: Sat, 14 Feb 2026 17:30:26 -0500 Subject: [PATCH 10/20] chore: more decimal digits --- src/db/dbQueryUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/db/dbQueryUtils.ts b/src/db/dbQueryUtils.ts index 90ec156..3639aef 100644 --- a/src/db/dbQueryUtils.ts +++ b/src/db/dbQueryUtils.ts @@ -191,7 +191,9 @@ export class QueryUtils { const ratings = await this.db .select({ starRating: - sql`cast(${avg(starReviewTable.starRating)} as decimal(2,1))`.mapWith( + // since all ratings are from 0 to 5, the whole part of the average cannot + // exceeds one. also we make the decimal digits longer for other consumers + sql`cast(${avg(starReviewTable.starRating)} as decimal(4,3))`.mapWith( Number, ), count: count(starReviewTable.id).mapWith(Number), From 45195643ebaafc82f14d5ab7df46e514b0631c66 Mon Sep 17 00:00:00 2001 From: stanleymw <107821509+stanleymw@users.noreply.github.com> Date: Sat, 14 Feb 2026 18:14:11 -0500 Subject: [PATCH 11/20] get report --- src/db/schema.ts | 2 +- src/endpoints/misc.ts | 1 + src/endpoints/reviews.ts | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/db/schema.ts b/src/db/schema.ts index 09bb7aa..1863f01 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -241,7 +241,7 @@ export const reportsTable = pgTable( createdAt: timestamp("created_at", { withTimezone: true, mode: "date", - }).defaultNow(), + }).notNull().defaultNow(), locationId: text("location_id") .references(() => locationDataTable.id, { onDelete: "cascade", diff --git a/src/endpoints/misc.ts b/src/endpoints/misc.ts index 82e19cc..ff2b3df 100644 --- a/src/endpoints/misc.ts +++ b/src/endpoints/misc.ts @@ -65,6 +65,7 @@ miscEndpoints.post( const userId = userDetails?.id; + await notifySlack(` User (${userId}) has reported an error with location ${location_id}: ${message} diff --git a/src/endpoints/reviews.ts b/src/endpoints/reviews.ts index 7198609..4fa5f34 100644 --- a/src/endpoints/reviews.ts +++ b/src/endpoints/reviews.ts @@ -1,5 +1,6 @@ import Elysia, { status, t } from "elysia"; import { fetchUserDetails } from "./auth"; +import { eq } from "drizzle-orm" import { addStarReview, deleteStarReview, @@ -9,6 +10,7 @@ import { updateTagReview, } from "db/reviews"; import { db } from "db/db"; +import { reportsTable } from "db/schema"; export const reviewEndpoints = new Elysia(); reviewEndpoints @@ -127,4 +129,24 @@ reviewEndpoints }) ), } + ) + .get( + "/v2/locations/:locationId/reports", + async ({ params: { locationId } }) => { + + const reports = await db.select().from(reportsTable).where(eq(reportsTable.locationId, locationId)) + console.log("lol", await db.select().from(reportsTable)) + return reports + }, + { + response: t.Array( + t.Object({ + id: t.Number(), + userId: t.Nullable(t.Number()), + createdAt: t.Date(), + locationId: t.String(), + message: t.String() + }) + ) + } ); From ae26237981fc1b27149718085824666452ea0be5 Mon Sep 17 00:00:00 2001 From: Aromia Date: Tue, 17 Feb 2026 21:54:28 -0500 Subject: [PATCH 12/20] test: test cases for ratingsAvg and ratingsCount --- tests/reviews.test.ts | 45 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/reviews.test.ts b/tests/reviews.test.ts index 7a596c2..0e7c9f9 100644 --- a/tests/reviews.test.ts +++ b/tests/reviews.test.ts @@ -380,4 +380,49 @@ describe("location review tests", () => { expect(allReviewsForLocation2).toHaveLength(0); } ); + + reviewTest.concurrent( + "ratingsAvg and ratingsCount test", + async ({ ctx: { db, locationId1, locationId2, user1, user2 } }) => { + const DB = new QueryUtils(db); + const [avgs, counts] = await DB.getRatingsAvgsAndCounts(); + expect(avgs[locationId1]).toBeUndefined(); + expect(counts[locationId1]).toBeUndefined(); + + await addStarReview(db, { + locationId: locationId1, + userId: user1.id, + rating: 3, + }); + await addStarReview(db, { + locationId: locationId1, + userId: user2.id, + rating: 5, + }); + let [avgs2, counts2] = await DB.getRatingsAvgsAndCounts(); + expect(avgs2[locationId1]).toBe(4); + expect(counts2[locationId1]).toBe(2); + + await addStarReview(db, { + locationId: locationId1, + userId: user1.id, + rating: 4, + }); + [avgs2, counts2] = await DB.getRatingsAvgsAndCounts(); + expect(avgs2[locationId1]).toBe(4.5); + expect(counts2[locationId1]).toBe(2); + + await deleteStarReview(db, { locationId: locationId1, userId: user1.id }); + [avgs2, counts2] = await DB.getRatingsAvgsAndCounts(); + expect(avgs2[locationId1]).toBe(5); + expect(counts2[locationId1]).toBe(1); + + await deleteStarReview(db, { locationId: locationId1, userId: user2.id }); + [avgs2, counts2] = await DB.getRatingsAvgsAndCounts(); + expect(avgs2[locationId1]).toBeUndefined(); + expect(counts2[locationId1]).toBeUndefined(); + expect(avgs2[locationId2]).toBeUndefined(); + expect(counts2[locationId2]).toBeUndefined(); + }, + ); }); From 85228b316f2a06f67e4750edde47029964aff518 Mon Sep 17 00:00:00 2001 From: Aromia Date: Tue, 17 Feb 2026 22:02:33 -0500 Subject: [PATCH 13/20] fix: test dependency --- tests/reviews.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/reviews.test.ts b/tests/reviews.test.ts index 0e7c9f9..30c240e 100644 --- a/tests/reviews.test.ts +++ b/tests/reviews.test.ts @@ -7,6 +7,7 @@ import { initializeTags, updateTagReview, } from "db/reviews"; +import { QueryUtils } from "db/dbQueryUtils"; import { dbTest } from "./dbstub"; import { createUserSession, DBUser, fetchUserSession } from "db/auth"; import { addLocationDataToDb } from "db/updateLocation"; From e622d693ed636987fe8f8de227960f8b74fd4968 Mon Sep 17 00:00:00 2001 From: stanleymw <107821509+stanleymw@users.noreply.github.com> Date: Sat, 21 Feb 2026 16:42:11 -0500 Subject: [PATCH 14/20] fix --- src/db/schema.ts | 2 +- src/endpoints/misc.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/db/schema.ts b/src/db/schema.ts index 1863f01..09bb7aa 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -241,7 +241,7 @@ export const reportsTable = pgTable( createdAt: timestamp("created_at", { withTimezone: true, mode: "date", - }).notNull().defaultNow(), + }).defaultNow(), locationId: text("location_id") .references(() => locationDataTable.id, { onDelete: "cascade", diff --git a/src/endpoints/misc.ts b/src/endpoints/misc.ts index ff2b3df..6847c83 100644 --- a/src/endpoints/misc.ts +++ b/src/endpoints/misc.ts @@ -70,7 +70,7 @@ miscEndpoints.post( User (${userId}) has reported an error with location ${location_id}: ${message} `, env.SLACK_MAIN_CHANNEL_WEBHOOK_URL); - db.insert(reportsTable).values({ + await db.insert(reportsTable).values({ locationId: location_id, message: message, userId: userId, From 573544f13086e23577e0976b42163bd3a0b9d361 Mon Sep 17 00:00:00 2001 From: stanleymw <107821509+stanleymw@users.noreply.github.com> Date: Sat, 21 Feb 2026 17:20:11 -0500 Subject: [PATCH 15/20] replace old endpoint, fix potential server crash --- src/db/schema.ts | 2 +- src/endpoints/misc.ts | 49 ++++++++++++++++++++-------------------- src/endpoints/reviews.ts | 3 +-- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/db/schema.ts b/src/db/schema.ts index 09bb7aa..1863f01 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -241,7 +241,7 @@ export const reportsTable = pgTable( createdAt: timestamp("created_at", { withTimezone: true, mode: "date", - }).defaultNow(), + }).notNull().defaultNow(), locationId: text("location_id") .references(() => locationDataTable.id, { onDelete: "cascade", diff --git a/src/endpoints/misc.ts b/src/endpoints/misc.ts index 6847c83..572e162 100644 --- a/src/endpoints/misc.ts +++ b/src/endpoints/misc.ts @@ -6,8 +6,9 @@ import { DateTime } from "luxon"; import { notifySlack } from "utils/slack"; import { LocationsSchema } from "./schemas"; import { env } from "env"; +import { eq } from "drizzle-orm" -import { reportsTable } from "db/schema"; +import { locationDataTable, reportsTable } from "db/schema"; import { fetchUserDetails } from "./auth"; import { sendEmail } from "utils/email"; @@ -58,18 +59,34 @@ miscEndpoints.post( ); miscEndpoints.post( - "/reportError", - async ({cookie, body: { location_id, message } }) => { + "/report", + async ({cookie, body: { location_id, message} }) => { const session = cookie["session_id"]!.value as string | undefined; const userDetails = await fetchUserDetails(session); const userId = userDetails?.id; + const reports = await db.select().from(locationDataTable).where(eq(locationDataTable.id, location_id)) + if (reports.length == 0) { + throw new Response(`Invalid location id ${location_id}`, { + status: 400, + }); + } + + if (reports.length > 1) { + throw new Response(` + Expected 1 restaurant corresponding to id=${location_id}. Somehow got 2. + `, {status: 500}) // this should be unreachable + } + + runBackgroundJobForErrorReport( + { + locationName: reports[0]?.name ?? "Unnamed", + locationId: location_id, + message: message + } + ).catch(console.error) - await notifySlack(` - User (${userId}) has reported an error with location ${location_id}: - ${message} - `, env.SLACK_MAIN_CHANNEL_WEBHOOK_URL); await db.insert(reportsTable).values({ locationId: location_id, message: message, @@ -79,7 +96,7 @@ miscEndpoints.post( { body: t.Object({ location_id: t.String(), - message: t.String(), + message: t.String({minLength: 1, maxLength: 512}), }), detail: { description: @@ -88,22 +105,6 @@ miscEndpoints.post( } ); -miscEndpoints.post( - "/report", - async ({ body: { message, locationId, locationName } }) => { - runBackgroundJobForErrorReport({ locationName, locationId, message }).catch( - console.error, - ); - return {}; - }, - { - body: t.Object({ - locationName: t.String(), - locationId: t.String(), - message: t.String({ maxLength: 300, minLength: 1 }), - }), - }, -); async function runBackgroundJobForErrorReport({ locationName, locationId, diff --git a/src/endpoints/reviews.ts b/src/endpoints/reviews.ts index 4fa5f34..3af5905 100644 --- a/src/endpoints/reviews.ts +++ b/src/endpoints/reviews.ts @@ -133,9 +133,8 @@ reviewEndpoints .get( "/v2/locations/:locationId/reports", async ({ params: { locationId } }) => { - const reports = await db.select().from(reportsTable).where(eq(reportsTable.locationId, locationId)) - console.log("lol", await db.select().from(reportsTable)) + return reports }, { From 7a1d88f71c0464b5e4302290a9b684d523a9a103 Mon Sep 17 00:00:00 2001 From: stanleymw <107821509+stanleymw@users.noreply.github.com> Date: Sat, 21 Feb 2026 17:38:28 -0500 Subject: [PATCH 16/20] move insert into createreport --- src/endpoints/misc.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/endpoints/misc.ts b/src/endpoints/misc.ts index 572e162..cb393c9 100644 --- a/src/endpoints/misc.ts +++ b/src/endpoints/misc.ts @@ -79,19 +79,14 @@ miscEndpoints.post( `, {status: 500}) // this should be unreachable } - runBackgroundJobForErrorReport( + createReport( { locationName: reports[0]?.name ?? "Unnamed", locationId: location_id, - message: message + message: message, + userId: userId, } ).catch(console.error) - - await db.insert(reportsTable).values({ - locationId: location_id, - message: message, - userId: userId, - }) }, { body: t.Object({ @@ -105,14 +100,16 @@ miscEndpoints.post( } ); -async function runBackgroundJobForErrorReport({ +async function createReport({ locationName, locationId, message, + userId, }: { locationName: string; locationId: string; message: string; + userId: number | undefined; }) { const received = await sendEmail( env.ALERT_EMAIL_SEND, @@ -124,4 +121,10 @@ async function runBackgroundJobForErrorReport({ `Report for ${locationName} (\`${locationId}\`): ${message} \nEmailed: ${received.join(", ")}`, env.SLACK_MAIN_CHANNEL_WEBHOOK_URL, ); + + await db.insert(reportsTable).values({ + locationId: locationId, + message: message, + userId: userId, + }) } From 4b848a8e8ff38ca7f7c0780a4228e91dfbdb4b65 Mon Sep 17 00:00:00 2001 From: stanleymw <107821509+stanleymw@users.noreply.github.com> Date: Sat, 21 Feb 2026 17:40:40 -0500 Subject: [PATCH 17/20] move db insert out, so response is more accurate --- src/endpoints/misc.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/endpoints/misc.ts b/src/endpoints/misc.ts index cb393c9..9d4d84b 100644 --- a/src/endpoints/misc.ts +++ b/src/endpoints/misc.ts @@ -84,9 +84,14 @@ miscEndpoints.post( locationName: reports[0]?.name ?? "Unnamed", locationId: location_id, message: message, - userId: userId, } ).catch(console.error) + + await db.insert(reportsTable).values({ + locationId: location_id, + message: message, + userId: userId, + }) }, { body: t.Object({ @@ -104,12 +109,10 @@ async function createReport({ locationName, locationId, message, - userId, }: { locationName: string; locationId: string; message: string; - userId: number | undefined; }) { const received = await sendEmail( env.ALERT_EMAIL_SEND, @@ -122,9 +125,4 @@ async function createReport({ env.SLACK_MAIN_CHANNEL_WEBHOOK_URL, ); - await db.insert(reportsTable).values({ - locationId: locationId, - message: message, - userId: userId, - }) } From 2513ed55aa33ed0f10073789d88d8c29057971fa Mon Sep 17 00:00:00 2001 From: stanleymw <107821509+stanleymw@users.noreply.github.com> Date: Sat, 21 Feb 2026 17:55:12 -0500 Subject: [PATCH 18/20] fix backward compatibility with old frontend --- src/endpoints/misc.ts | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/endpoints/misc.ts b/src/endpoints/misc.ts index 9d4d84b..88dd4e3 100644 --- a/src/endpoints/misc.ts +++ b/src/endpoints/misc.ts @@ -60,48 +60,49 @@ miscEndpoints.post( miscEndpoints.post( "/report", - async ({cookie, body: { location_id, message} }) => { + async ({ cookie, body: { locationId, message } }) => { const session = cookie["session_id"]!.value as string | undefined; const userDetails = await fetchUserDetails(session); const userId = userDetails?.id; - const reports = await db.select().from(locationDataTable).where(eq(locationDataTable.id, location_id)) + const reports = await db.select().from(locationDataTable).where(eq(locationDataTable.id, locationId)) if (reports.length == 0) { - throw new Response(`Invalid location id ${location_id}`, { - status: 400, - }); + throw new Response(`Invalid location id ${locationId}`, { + status: 400, + }); } if (reports.length > 1) { throw new Response(` - Expected 1 restaurant corresponding to id=${location_id}. Somehow got 2. - `, {status: 500}) // this should be unreachable + Expected 1 restaurant corresponding to id=${locationId}. Somehow got 2. + `, { status: 500 }) // this should be unreachable } + const locationName = reports[0]?.name ?? "Unnamed" createReport( { - locationName: reports[0]?.name ?? "Unnamed", - locationId: location_id, - message: message, + locationName, + locationId, + message, } ).catch(console.error) await db.insert(reportsTable).values({ - locationId: location_id, - message: message, - userId: userId, + locationId, + message, + userId, }) }, { body: t.Object({ - location_id: t.String(), - message: t.String({minLength: 1, maxLength: 512}), + locationId: t.String(), + message: t.String({ minLength: 1, maxLength: 512 }), }), detail: { description: "Endpoint for reporting errors in information", - }, + }, } ); From 5f1f21f5d3b668f76a49df9abed980459d55b9ea Mon Sep 17 00:00:00 2001 From: stanleymw <107821509+stanleymw@users.noreply.github.com> Date: Sat, 21 Feb 2026 18:10:32 -0500 Subject: [PATCH 19/20] return empty object fix --- src/endpoints/misc.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/endpoints/misc.ts b/src/endpoints/misc.ts index 88dd4e3..9590e1f 100644 --- a/src/endpoints/misc.ts +++ b/src/endpoints/misc.ts @@ -93,6 +93,8 @@ miscEndpoints.post( message, userId, }) + + return {} }, { body: t.Object({ From 35a9f85e0b9fb4eb243778634329b784b1b20bca Mon Sep 17 00:00:00 2001 From: stanleymw <107821509+stanleymw@users.noreply.github.com> Date: Sat, 21 Feb 2026 18:21:02 -0500 Subject: [PATCH 20/20] db generate --- drizzle/0010_shallow_doctor_strange.sql | 10 + drizzle/meta/0010_snapshot.json | 1115 +++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + 3 files changed, 1132 insertions(+) create mode 100644 drizzle/0010_shallow_doctor_strange.sql create mode 100644 drizzle/meta/0010_snapshot.json diff --git a/drizzle/0010_shallow_doctor_strange.sql b/drizzle/0010_shallow_doctor_strange.sql new file mode 100644 index 0000000..dd9e60a --- /dev/null +++ b/drizzle/0010_shallow_doctor_strange.sql @@ -0,0 +1,10 @@ +CREATE TABLE "reports" ( + "id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "reports_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1), + "user_id" integer, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "location_id" text NOT NULL, + "message" text NOT NULL +); +--> statement-breakpoint +ALTER TABLE "reports" ADD CONSTRAINT "reports_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "reports" ADD CONSTRAINT "reports_location_id_location_data_id_fk" FOREIGN KEY ("location_id") REFERENCES "public"."location_data"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/drizzle/meta/0010_snapshot.json b/drizzle/meta/0010_snapshot.json new file mode 100644 index 0000000..ced89bc --- /dev/null +++ b/drizzle/meta/0010_snapshot.json @@ -0,0 +1,1115 @@ +{ + "id": "f2f47465-b027-4494-b217-1e6ca9c4ce8e", + "prevId": "99fa16e2-e656-421e-aad6-02437afea8ce", + "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.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 1d6beab..46b2076 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -71,6 +71,13 @@ "when": 1769646373776, "tag": "0009_cooing_snowbird", "breakpoints": true + }, + { + "idx": 10, + "version": "7", + "when": 1771716042342, + "tag": "0010_shallow_doctor_strange", + "breakpoints": true } ] } \ No newline at end of file