All files / routes/partner share.routes.ts

94.73% Statements 18/19
100% Branches 8/8
100% Functions 1/1
93.75% Lines 15/16

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63                                              2x       2x 5x 5x   5x 5x   4x 1x                 3x 8x 8x 8x 2x         6x 1x                
import { Hono } from "hono";
import type { Dal } from "../../dal";
import { isUniqueViolation } from "../../dal/partners/partners.dal";
import { generateReferralCode } from "../../lib/partners/code";
import { goOrigin } from "../../lib/partners/origin";
 
/**
 * The partner's open share link — `go.interioring.com/r/<code>` (FR-P-6.1).
 *
 * Unlike a referral, this names no homeowner. It is the link a partner drops
 * into a society WhatsApp group or their status: anyone who opens it and
 * messages us becomes a `link`-mode referral attributed to them, minted at
 * that moment from the inbound (see services/partners/engagement.service.ts).
 * It pays exactly like a directly-referred one (FR-P-6.3).
 *
 * POST, not GET, because the first call MINTS the code. A GET that writes is
 * the kind of thing a prefetcher or a retry turns into a surprise.
 */
type Env = {
	Bindings: CloudflareBindings;
	Variables: { dal: Dal; partnerId: string };
};
 
const share = new Hono<Env>();
 
// POST /api/partner/share/link — idempotent: returns the existing code, or
// mints one on first use.
share.post("/link", async (c) => {
	const dal = c.get("dal");
	const partnerId = c.get("partnerId");
 
	const partner = await dal.partners.findById(partnerId);
	if (!partner) return c.json({ error: "not_found" }, 404);
 
	if (partner.shareCode) {
		return c.json({
			code: partner.shareCode,
			url: `${goOrigin(c.env.ENVIRONMENT)}/r/${partner.shareCode}`,
		});
	}
 
	// Retry on collision, exactly as referral creation does — the partial
	// unique index is what makes a clash a retry rather than two partners
	// sharing one link.
	for (let attempt = 0; attempt < 5; attempt++) {
		const code = generateReferralCode();
		try {
			await dal.partners.update(partnerId, { shareCode: code });
			return c.json({
				code,
				url: `${goOrigin(c.env.ENVIRONMENT)}/r/${code}`,
			});
		} catch (error) {
			if (attempt < 4 && isUniqueViolation(error)) continue;
			throw error;
		}
	}
 
	throw new Error("Could not allocate a unique share code after 5 attempts");
});
 
export default share;