Skip to content

Commit c626296

Browse files
ralyodioclaude
andauthored
affiliate: store a merchant with update-or-insert (#255)
* affiliate: store a merchant with update-or-insert, since upsert cannot target lower(origin) Registering a merchant in the directory answered "there is no unique or exclusion constraint matching the ON CONFLICT specification" in production: the unique index is on lower(origin) and PostgREST upsert only names columns. The fallback handled the update but not the first insert. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CDEiDss9RWYibtmxSk5Gr2 * affiliate: the same update-or-insert for joins, whose index is also on lower(origin) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CDEiDss9RWYibtmxSk5Gr2 --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 60e8d25 commit c626296

1 file changed

Lines changed: 17 additions & 38 deletions

File tree

‎lib/affiliate/directory.ts‎

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -90,36 +90,17 @@ export async function addOrRefreshProgram(input: string, addedBy: string | null)
9090
}
9191
return { ok: false, error: found.error };
9292
}
93-
const { data, error } = await svc
94-
.from("affiliate_programs")
95-
.upsert(
96-
{
97-
origin: found.origin,
98-
descriptor: found.raw,
99-
verified: found.verified,
100-
fetched_at: now,
101-
error: null,
102-
added_by: addedBy,
103-
updated_at: now,
104-
},
105-
{ onConflict: "origin" },
106-
)
107-
.select("id, origin, descriptor, verified, fetched_at, error")
108-
.single();
109-
if (error || !data) {
110-
// The unique index is on lower(origin), which upsert cannot target; fall back to update-or-insert.
111-
const existing = await directoryRow(found.origin);
112-
if (existing) {
113-
await svc
114-
.from("affiliate_programs")
115-
.update({ descriptor: found.raw, verified: found.verified, fetched_at: now, error: null, updated_at: now })
116-
.eq("id", existing.id);
117-
const row = await directoryRow(found.origin);
118-
return row ? { ok: true, row, warnings: found.warnings } : { ok: false, error: "Could not store the program." };
119-
}
120-
return { ok: false, error: error?.message ?? "Could not store the program." };
121-
}
122-
return { ok: true, row: toRow(data), warnings: found.warnings };
93+
// The unique index is on lower(origin), which a PostgREST upsert cannot
94+
// name, so: update the row that exists, else insert.
95+
const existing = await directoryRow(found.origin);
96+
const patch = { descriptor: found.raw, verified: found.verified, fetched_at: now, error: null, updated_at: now };
97+
const { error } = existing
98+
? await svc.from("affiliate_programs").update(patch).eq("id", existing.id)
99+
: await svc.from("affiliate_programs").insert({ origin: found.origin, added_by: addedBy, ...patch });
100+
if (error) return { ok: false, error: error.message };
101+
const row = await directoryRow(found.origin);
102+
if (!row) return { ok: false, error: "Could not store the program." };
103+
return { ok: true, row, warnings: found.warnings };
123104
}
124105

125106
/** Re-read every merchant not read in the last day (spec, "Directories" rule 1). */
@@ -233,14 +214,12 @@ export async function joinExternal(
233214
const mine = await ensureMembershipForUser(user);
234215
if (!mine) return { ok: false, error: "Could not create your CrawlProof affiliate profile." };
235216

236-
const { data: inserted, error: insErr } = await svc
237-
.from("affiliate_joins")
238-
.upsert(
239-
{ owner_id: user.id, origin: row.origin, program_id: program.id, status: "pending", terms: program.pays, updated_at: new Date().toISOString() },
240-
{ onConflict: "owner_id,origin,program_id", ignoreDuplicates: false },
241-
)
242-
.select(JOIN_COLUMNS)
243-
.single();
217+
// Same lower(origin) index as the directory: a refused or ended twin is
218+
// reopened in place, otherwise a fresh row is inserted.
219+
const fresh = { owner_id: user.id, origin: row.origin, program_id: program.id, status: "pending", terms: program.pays, error: null, updated_at: new Date().toISOString() };
220+
const { data: inserted, error: insErr } = twin
221+
? await svc.from("affiliate_joins").update(fresh).eq("id", twin.id).select(JOIN_COLUMNS).single()
222+
: await svc.from("affiliate_joins").insert(fresh).select(JOIN_COLUMNS).single();
244223
if (insErr || !inserted) return { ok: false, error: insErr?.message ?? "Could not start the join." };
245224

246225
const site = env.siteUrl.replace(/\/$/, "");

0 commit comments

Comments
 (0)