Skip to content

[codex] Prevent empty affiliate offer slugs#415

Merged
ralyodio merged 1 commit into
profullstack:masterfrom
jsdavid278-cyber:codex/ugig-affiliate-empty-slug
Jun 6, 2026
Merged

[codex] Prevent empty affiliate offer slugs#415
ralyodio merged 1 commit into
profullstack:masterfrom
jsdavid278-cyber:codex/ugig-affiliate-empty-slug

Conversation

@jsdavid278-cyber
Copy link
Copy Markdown
Contributor

Summary

  • prevent affiliate offer creation from persisting an empty slug
  • use offer as the fallback when a valid title has no slug-safe characters
  • add a regression test for an emoji-only title that previously inserted slug: ""

Fixes #414.

Validation

  • npm.cmd run test:run -- src/app/api/affiliates/offers/route.test.ts
  • npm.cmd run type-check

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jun 6, 2026

Greptile Summary

This PR fixes issue #414 by adding a "offer" fallback in slugify() so that titles containing no ASCII-alphanumeric characters (e.g. emoji-only strings) no longer persist an empty slug to the database. A regression test for an emoji-only title is included.

  • route.ts: slugify() now stores the derived slug in a variable and returns slug || \"offer\", ensuring the result is always non-empty.
  • route.test.ts: New test mocks a \"🔥🔥🔥\" title POST and asserts the inserted slug is \"offer\" rather than \"\".

Confidence Score: 3/5

The empty-slug regression is correctly closed, but the hardcoded constant 'offer' makes concurrent emoji-only offer creation reliably collide on the same slug before the non-atomic uniqueness check can intervene.

Every non-slugifiable title now funnels to the exact same base slug. Two simultaneous requests both compute 'offer', both query the DB and find no existing row, and then both attempt to insert — one will receive a unique-constraint error surfaced as a 400 to the user. This race is rare for normal titles; it becomes reproducible here because all such titles converge on a single hardcoded word.

src/app/api/affiliates/offers/route.ts — specifically the fallback value returned by slugify().

Important Files Changed

Filename Overview
src/app/api/affiliates/offers/route.ts Adds "offer" fallback in slugify() when all characters are stripped; fix is correct but the hardcoded constant funnels every non-slugifiable title to the same base slug, amplifying the pre-existing check-then-insert race.
src/app/api/affiliates/offers/route.test.ts Adds a regression test verifying the emoji-only title path inserts slug: "offer" instead of the empty string; mock setup correctly captures the insert argument.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[POST /api/affiliates/offers] --> B[validateOfferInput]
    B -->|invalid| C[400 Bad Request]
    B -->|valid| D["slugify(input.title)"]
    D --> E{slug empty?}
    E -->|no| F[slug = derived slug]
    E -->|yes emoji-only title| G[slug = 'offer']
    F --> H[Check DB: slug exists?]
    G --> H
    H -->|no collision| I[INSERT with slug]
    H -->|collision| J["slug = slug + '-' + random8"]
    J --> I
    I -->|success| K[201 Created]
    I -->|error| L[400 DB Error]
Loading

Reviews (1): Last reviewed commit: "fix: prevent empty affiliate offer slugs" | Re-trigger Greptile

.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
.slice(0, 80);
return slug || "offer";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 All titles that produce no slug-safe characters (emoji, pure punctuation like "---", CJK-only strings pre-Unicode normalization, etc.) collapse to the same fallback "offer". Because the uniqueness check and the insert are not atomic, two concurrent requests that both generate "offer" will both pass the if (existing) guard and race to insert — one will hit a unique-constraint violation. For normal titles this race is rare since each user's title is different; here every non-slugifiable title funnels to the same word, so concurrent emoji-only offers will trigger it reliably. Adding a short random suffix to the fallback itself (before the collision check runs) makes all fallback slugs distinct by default and sidesteps the race entirely.

Suggested change
return slug || "offer";
return slug || `offer-${Math.random().toString(36).slice(2, 8)}`;

@ralyodio ralyodio merged commit b6b1ad1 into profullstack:master Jun 6, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Affiliate offers can be created with an empty slug

2 participants