feat(pitch-deck): branded short links for magic URLs (pitch.breakpilot.ai/p/ab3xk2)
Build pitch-deck / build-push-deploy (push) Successful in 1m31s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 30s
Build pitch-deck / build-push-deploy (push) Successful in 1m31s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 30s
- New pitch_short_links table stores 6-char alphanumeric codes mapped to magic link tokens - GET /p/[code] redirects to /auth/verify?token=... (302, validates expiry) - All magic link generation points (invite, generate-link, resend) now create a short code - Emails (invite + resend) use the short URL — less token-like, cleaner for spam filters - Copy-link UI shows short URL prominently with full URL as fallback - Migration 008 added to /api/admin/migrate Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import pool from '@/lib/db'
|
||||
import { generateToken } from '@/lib/auth'
|
||||
import { requireAdmin, logAdminAudit } from '@/lib/admin-auth'
|
||||
import { createShortLink } from '@/lib/short-links'
|
||||
|
||||
interface RouteContext {
|
||||
params: Promise<{ id: string }>
|
||||
@@ -47,6 +48,7 @@ export async function POST(request: NextRequest, ctx: RouteContext) {
|
||||
|
||||
const baseUrl = process.env.PITCH_BASE_URL || 'https://pitch.breakpilot.ai'
|
||||
const url = `${baseUrl}/auth/verify?token=${token}`
|
||||
const shortUrl = await createShortLink(token)
|
||||
|
||||
await logAdminAudit(
|
||||
adminId,
|
||||
@@ -56,5 +58,5 @@ export async function POST(request: NextRequest, ctx: RouteContext) {
|
||||
investor.id,
|
||||
)
|
||||
|
||||
return NextResponse.json({ url, expires_at: expiresAt.toISOString() })
|
||||
return NextResponse.json({ url, short_url: shortUrl, expires_at: expiresAt.toISOString() })
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { generateToken } from '@/lib/auth'
|
||||
import { requireAdmin, logAdminAudit } from '@/lib/admin-auth'
|
||||
import { sendMagicLinkEmail } from '@/lib/email'
|
||||
import { checkRateLimit, RATE_LIMITS } from '@/lib/rate-limit'
|
||||
import { createShortLink } from '@/lib/short-links'
|
||||
|
||||
interface RouteContext {
|
||||
params: Promise<{ id: string }>
|
||||
@@ -44,9 +45,8 @@ export async function POST(request: NextRequest, ctx: RouteContext) {
|
||||
[investor.id, token, expiresAt],
|
||||
)
|
||||
|
||||
const baseUrl = process.env.PITCH_BASE_URL || 'https://pitch.breakpilot.ai'
|
||||
const magicLinkUrl = `${baseUrl}/auth/verify?token=${token}`
|
||||
await sendMagicLinkEmail(investor.email, investor.name, magicLinkUrl)
|
||||
const shortUrl = await createShortLink(token)
|
||||
await sendMagicLinkEmail(investor.email, investor.name, shortUrl)
|
||||
|
||||
await logAdminAudit(
|
||||
adminId,
|
||||
|
||||
@@ -3,6 +3,7 @@ import pool from '@/lib/db'
|
||||
import { generateToken } from '@/lib/auth'
|
||||
import { requireAdmin, logAdminAudit } from '@/lib/admin-auth'
|
||||
import { sendMagicLinkEmail } from '@/lib/email'
|
||||
import { createShortLink } from '@/lib/short-links'
|
||||
import { checkRateLimit, RATE_LIMITS } from '@/lib/rate-limit'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
@@ -72,9 +73,10 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const baseUrl = process.env.PITCH_BASE_URL || 'https://pitch.breakpilot.ai'
|
||||
const magicLinkUrl = `${baseUrl}/auth/verify?token=${token}`
|
||||
const shortUrl = await createShortLink(token)
|
||||
|
||||
if (send_email) {
|
||||
await sendMagicLinkEmail(normalizedEmail, name || null, magicLinkUrl, greeting, message, closing, normalizedLang)
|
||||
await sendMagicLinkEmail(normalizedEmail, name || null, shortUrl, greeting, message, closing, normalizedLang)
|
||||
}
|
||||
|
||||
await logAdminAudit(
|
||||
@@ -99,5 +101,6 @@ export async function POST(request: NextRequest) {
|
||||
email: normalizedEmail,
|
||||
expires_at: expiresAt.toISOString(),
|
||||
magic_link_url: magicLinkUrl,
|
||||
short_url: shortUrl,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -162,6 +162,14 @@ export async function POST(request: NextRequest) {
|
||||
`ALTER TABLE dataroom_investor_uploads ADD COLUMN IF NOT EXISTS description_en TEXT`,
|
||||
// 007 — investor preferred language
|
||||
`ALTER TABLE pitch_investors ADD COLUMN IF NOT EXISTS preferred_lang VARCHAR(5) NOT NULL DEFAULT 'de'`,
|
||||
// 008 — short links for magic URLs
|
||||
`CREATE TABLE IF NOT EXISTS pitch_short_links (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
short_code VARCHAR(10) UNIQUE NOT NULL,
|
||||
token VARCHAR(128) NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_pitch_short_links_code ON pitch_short_links(short_code)`,
|
||||
]
|
||||
|
||||
for (const sql of statements) {
|
||||
|
||||
Reference in New Issue
Block a user