Skip to content

Commit

Permalink
Associate images on alias bottle ID changes
Browse files Browse the repository at this point in the history
  • Loading branch information
dcramer committed Aug 24, 2024
1 parent 635ffff commit fb45a08
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 6 deletions.
9 changes: 7 additions & 2 deletions apps/server/src/lib/test/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,7 @@ export const StorePrice = async (
if (!data.price)
data.price =
parseInt(faker.finance.amount({ min: 50, max: 200, dec: 0 }), 10) * 100;

if (!data.url) data.url = faker.internet.url();

if (!data.externalSiteId)
Expand All @@ -659,10 +660,14 @@ export const StorePrice = async (

if (!data.currency) data.currency = "usd";

if (data.hidden === undefined) data.hidden = false;

if (data.bottleId === undefined) data.bottleId = (await Bottle({}, tx)).id;

const { rows } = await tx.execute<dbSchema.StorePrice>(
sql`
INSERT INTO ${storePrices} (bottle_id, external_site_id, name, volume, price, currency, url)
VALUES (${data.bottleId}, ${data.externalSiteId}, ${data.name}, ${data.volume}, ${data.price}, ${data.currency}, ${data.url})
INSERT INTO ${storePrices} (bottle_id, external_site_id, name, volume, price, currency, url, hidden, image_url)
VALUES (${data.bottleId}, ${data.externalSiteId}, ${data.name}, ${data.volume}, ${data.price}, ${data.currency}, ${data.url}, ${data.hidden}, ${data.imageUrl ?? null})
ON CONFLICT (external_site_id, LOWER(name), volume)
DO UPDATE
SET bottle_id = COALESCE(excluded.bottle_id, ${storePrices.bottleId}),
Expand Down
161 changes: 161 additions & 0 deletions apps/server/src/trpc/routes/bottleAliasUpsert.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { db } from "@peated/server/db";
import {
bottleAliases,
bottles,
reviews,
storePrices,
} from "@peated/server/db/schema";
import waitError from "@peated/server/lib/test/waitError";
import * as workerClient from "@peated/server/worker/client";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { createCaller } from "../router";

// Mock the worker client
vi.mock("@peated/server/worker/client");

describe("bottleAliasUpsert", () => {
beforeEach(() => {
vi.resetAllMocks();
});

test("creates a new bottle alias", async ({ fixtures }) => {
const bottle = await fixtures.Bottle();
const user = await fixtures.User({ mod: true });
const caller = createCaller({ user });

const result = await caller.bottleAliasUpsert({
bottle: bottle.id,
name: "New Alias",
});

expect(result).toEqual({});

const alias = await db.query.bottleAliases.findFirst({
where: eq(bottleAliases.name, "New Alias"),
});

expect(alias).toBeDefined();
expect(alias?.bottleId).toBe(bottle.id);
});

test("updates store prices with matching name", async ({ fixtures }) => {
const bottle = await fixtures.Bottle();
const storePrice = await fixtures.StorePrice({
name: "Test Alias",
bottleId: null,
});
const user = await fixtures.User({ mod: true });
const caller = createCaller({ user });

await caller.bottleAliasUpsert({
bottle: bottle.id,
name: "Test Alias",
});

const updatedStorePrice = await db.query.storePrices.findFirst({
where: eq(storePrices.id, storePrice.id),
});

expect(updatedStorePrice?.bottleId).toBe(bottle.id);
});

test("updates reviews with matching name", async ({ fixtures }) => {
const bottle = await fixtures.Bottle();
const review = await fixtures.Review({
name: "Test Alias",
bottleId: null,
});
const user = await fixtures.User({ mod: true });
const caller = createCaller({ user });

await caller.bottleAliasUpsert({
bottle: bottle.id,
name: "Test Alias",
});

const updatedReview = await db.query.reviews.findFirst({
where: eq(reviews.id, review.id),
});

expect(updatedReview?.bottleId).toBe(bottle.id);
});

test("updates bottle image if store price has an image", async ({
fixtures,
}) => {
const bottle = await fixtures.Bottle({ imageUrl: null });
const storePrice = await fixtures.StorePrice({
name: "Test Alias",
bottleId: null,
imageUrl: "https://example.com/image.jpg",
});
const user = await fixtures.User({ mod: true });
const caller = createCaller({ user });

await caller.bottleAliasUpsert({
bottle: bottle.id,
name: "Test Alias",
});

const updatedBottle = await db.query.bottles.findFirst({
where: eq(bottles.id, bottle.id),
});

expect(updatedBottle?.imageUrl).toBe("https://example.com/image.jpg");
});

test("throws NOT_FOUND for non-existent bottle", async ({ fixtures }) => {
const user = await fixtures.User({ mod: true });
const caller = createCaller({ user });

const err = await waitError(
caller.bottleAliasUpsert({
bottle: 9999,
name: "Test Alias",
}),
);

expect(err).toMatchInlineSnapshot(`[TRPCError: Bottle not found.]`);
});

test("requires mod permission", async ({ fixtures }) => {
const bottle = await fixtures.Bottle();
const user = await fixtures.User({ mod: false });
const caller = createCaller({ user });

const err = await waitError(
caller.bottleAliasUpsert({
bottle: bottle.id,
name: "Test Alias",
}),
);

expect(err).toMatchInlineSnapshot(`[TRPCError: UNAUTHORIZED]`);
});

test("throws error for duplicate alias on different bottle", async ({
fixtures,
}) => {
const bottle1 = await fixtures.Bottle();
const bottle2 = await fixtures.Bottle();
await fixtures.BottleAlias({
bottleId: bottle1.id,
name: "Duplicate Alias",
});
const user = await fixtures.User({ mod: true });
const caller = createCaller({ user });

const err = await waitError(
caller.bottleAliasUpsert({
bottle: bottle2.id,
name: "Duplicate Alias",
}),
);

expect(err).toMatchInlineSnapshot(
`[TRPCError: Duplicate alias found (1). Not implemented.]`,
);
});
});
22 changes: 20 additions & 2 deletions apps/server/src/trpc/routes/bottleAliasUpsert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,30 @@ export default modProcedure.input(BottleAliasSchema).mutation(async function ({
);
}

await tx
const [price] = await tx
.update(storePrices)
.set({
bottleId: input.bottle,
})
.where(eq(sql`LOWER(${storePrices.name})`, input.name.toLowerCase()));
.where(eq(sql`LOWER(${storePrices.name})`, input.name.toLowerCase()))
.returning();

if (price?.imageUrl && input.bottle) {
// determine if we've got an image we can use
const [bottle] = await tx
.select()
.from(bottles)
.where(eq(bottles.id, input.bottle));

if (bottle && !bottle.imageUrl) {
await tx
.update(bottles)
.set({
imageUrl: price.imageUrl,
})
.where(eq(bottles.id, input.bottle));
}
}

await tx
.update(reviews)
Expand Down
24 changes: 22 additions & 2 deletions apps/server/src/worker/jobs/capturePriceImage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { defaultHeaders } from "@peated/server/constants";
import { db } from "@peated/server/db";
import { storePrices } from "@peated/server/db/schema";
import { compressAndResizeImage, storeFile } from "@peated/server/lib/uploads";
import { bottles, storePrices } from "@peated/server/db/schema";
import {
compressAndResizeImage,
copyFile,
storeFile,
} from "@peated/server/lib/uploads";
import { eq } from "drizzle-orm";
import { Readable } from "stream";

Expand Down Expand Up @@ -54,4 +58,20 @@ export default async ({
imageUrl: newImageUrl,
})
.where(eq(storePrices.id, priceId));

if (price.bottleId) {
const [bottle] = await db
.select()
.from(bottles)
.where(eq(bottles.id, price.bottleId));

if (bottle && !bottle.imageUrl) {
await db
.update(bottles)
.set({
imageUrl: newImageUrl,
})
.where(eq(bottles.id, price.bottleId));
}
}
};

0 comments on commit fb45a08

Please sign in to comment.