Skip to content

Commit 1bfccf3

Browse files
fix: hide pre-launch active promotions from public slug lookups
A promotion activated ahead of its window is no longer readable via GET /api/v1/promotions/{slug} until startsAt, matching the list endpoint's visibility contract. Ended promotions stay visible. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 6348f13 commit 1bfccf3

2 files changed

Lines changed: 9 additions & 2 deletions

File tree

convex/promotions.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,11 @@ describe("promotions.getBySlugPublicInternal", () => {
334334
expect(await getBySlugHandler(ctx, { slug: base.slug, now: 150 })).toBeNull();
335335
});
336336

337+
it("hides active promotions before their launch window", async () => {
338+
const ctx = makeQueryCtx({ ...base, status: "active" });
339+
expect(await getBySlugHandler(ctx, { slug: base.slug, now: 50 })).toBeNull();
340+
});
341+
337342
it("returns ended promotions with active=false", async () => {
338343
const ctx = makeQueryCtx({ ...base, status: "ended" });
339344
const result = (await getBySlugHandler(ctx, { slug: base.slug, now: 150 })) as {

convex/promotions.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,11 @@ export const getBySlugPublicInternal = internalQuery({
398398
args: { slug: v.string(), now: v.number() },
399399
handler: async (ctx, args) => {
400400
const promotion = await getPromotionBySlug(ctx, normalizePromotionSlug(args.slug));
401-
// Drafts stay hidden until launch; ended promotions remain visible so
402-
// launch pages and stale CLI links can render a clear "ended" state.
401+
// Drafts and pre-launch activations stay hidden so unreleased launch
402+
// details cannot be read by guessing the slug; ended promotions remain
403+
// visible so launch pages and stale CLI links can render an "ended" state.
403404
if (!promotion || promotion.status === "draft") return null;
405+
if (promotion.status === "active" && args.now < promotion.startsAt) return null;
404406
return toPublicPromotion(promotion, args.now);
405407
},
406408
});

0 commit comments

Comments
 (0)