import { eq } from 'drizzle-orm'; import { db } from '@/lib/db'; import { env } from '@/lib/env'; import { trackedLinks, type NewTrackedLink } from '@/lib/db/schema/tracked-links'; /** * Phase 4c — service-layer helpers for tracked redirect links. Use * `createTrackedLink` from any email-composer flow to wrap an outbound * URL in a `/q/` short-link that records click-throughs. * * Slug format: random URL-safe ID. Short enough not to overwhelm an * inbox preview pane but long enough that collision probability is * negligible across the lifetime of the system. */ function generateSlug(): string { // 8 random bytes → 11-char base64url string. Collision probability // across 1M links: ~1e-7. The DB unique index is the backstop. const bytes = crypto.getRandomValues(new Uint8Array(8)); return btoa(String.fromCharCode(...bytes)) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, ''); } export interface CreateTrackedLinkInput { portId: string; targetUrl: string; /** Optional FK to `document_sends.id` so per-email click-throughs are * attributable. Leave null for one-off short links. */ sendId?: string; createdByUserId?: string; } export async function createTrackedLink(input: CreateTrackedLinkInput) { // Retry on slug collision (extremely rare). Three attempts is more // than enough — at our slug entropy a single collision in 1M links // would be a once-per-century event. for (let attempt = 0; attempt < 3; attempt++) { const slug = generateSlug(); try { const values: NewTrackedLink = { portId: input.portId, slug, targetUrl: input.targetUrl, ...(input.sendId ? { sendId: input.sendId } : {}), ...(input.createdByUserId ? { createdByUserId: input.createdByUserId } : {}), }; const [row] = await db.insert(trackedLinks).values(values).returning(); return row!; } catch (err) { const msg = err instanceof Error ? err.message : String(err); if (msg.includes('uniq_tracked_links_slug') && attempt < 2) continue; throw err; } } throw new Error('Failed to mint a unique tracked-link slug after 3 attempts'); } /** Build the public-facing tracked URL for an existing record. */ export function buildTrackedUrl(slug: string): string { const base = env.NEXT_PUBLIC_APP_URL.replace(/\/$/, ''); return `${base}/q/${slug}`; } /** Look up click stats for a single tracked link. */ export async function getTrackedLink(id: string) { return db.query.trackedLinks.findFirst({ where: eq(trackedLinks.id, id) }); }