- Add complete Cloudflare Worker implementation with waitlist API endpoints - Integrate Cloudflare D1 SQLite database for production waitlist storage - Add wrangler configuration for Worker deployment and D1 binding - Rewrite README with comprehensive deployment and architecture documentation - Update environment configuration and build setup - Add waitlist utility functions and API client - Update project dependencies and Vite configuration - Deploy live to https://react-example.white-glade-ab2c.workers.dev
138 lines
4.0 KiB
TypeScript
138 lines
4.0 KiB
TypeScript
interface Env {
|
|
DB: D1Database;
|
|
}
|
|
|
|
const BASE_WAITLIST_OFFSET = 2870;
|
|
const CORS_HEADERS = {
|
|
'Access-Control-Allow-Headers': 'Content-Type',
|
|
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Content-Type': 'application/json'
|
|
};
|
|
|
|
function json(data: unknown, status = 200) {
|
|
return new Response(JSON.stringify(data), {
|
|
status,
|
|
headers: CORS_HEADERS
|
|
});
|
|
}
|
|
|
|
function makeReferralCode() {
|
|
return `SX-${crypto.randomUUID().replace(/-/g, '').slice(0, 5).toUpperCase()}`;
|
|
}
|
|
|
|
async function getStats(env: Env) {
|
|
const row = await env.DB.prepare(
|
|
'SELECT total_registered, total_funded_amount FROM waitlist_stats WHERE id = ?'
|
|
)
|
|
.bind('waitlist')
|
|
.first<{ total_registered: number; total_funded_amount: number }>();
|
|
|
|
const registered = Number(row?.total_registered ?? 0);
|
|
return {
|
|
totalCount: BASE_WAITLIST_OFFSET + registered,
|
|
totalFundedAmount: Number(row?.total_funded_amount ?? 142500),
|
|
recentRegistrationsCount: registered
|
|
};
|
|
}
|
|
|
|
async function register(request: Request, env: Env) {
|
|
const body = await request.json() as {
|
|
email?: string;
|
|
fullName?: string;
|
|
tier?: string;
|
|
investmentAmount?: number;
|
|
referredBy?: string;
|
|
};
|
|
|
|
const email = body.email?.trim().toLowerCase();
|
|
if (!email) return json({ error: 'A valid email is required.' }, 400);
|
|
|
|
const fullName = body.fullName?.trim() || 'BM Connect Member';
|
|
const tier = body.tier || 'Supporter';
|
|
const investmentAmount = Number(body.investmentAmount) || 25;
|
|
const referredBy = body.referredBy?.trim() || null;
|
|
const id = crypto.randomUUID();
|
|
const referralCode = makeReferralCode();
|
|
|
|
const result = await env.DB.batch([
|
|
env.DB.prepare(
|
|
`UPDATE waitlist_stats
|
|
SET total_registered = total_registered + 1,
|
|
total_funded_amount = total_funded_amount + ?,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?`
|
|
).bind(investmentAmount, 'waitlist'),
|
|
env.DB.prepare(
|
|
`INSERT INTO waitlist_entries
|
|
(id, email, full_name, tier, investment_amount, position, referral_code, referred_by)
|
|
SELECT ?, ?, ?, ?, ?, ? + total_registered, ?, ?
|
|
FROM waitlist_stats
|
|
WHERE id = ?`
|
|
).bind(
|
|
id,
|
|
email,
|
|
fullName,
|
|
tier,
|
|
investmentAmount,
|
|
BASE_WAITLIST_OFFSET,
|
|
referralCode,
|
|
referredBy,
|
|
'waitlist'
|
|
)
|
|
]);
|
|
|
|
if (!result[1].success) {
|
|
throw new Error('The waitlist entry could not be created.');
|
|
}
|
|
|
|
const entry = await env.DB.prepare(
|
|
`SELECT id, email, full_name, tier, investment_amount, position, referral_code, referred_by, created_at
|
|
FROM waitlist_entries WHERE id = ?`
|
|
).bind(id).first();
|
|
|
|
return json({
|
|
id,
|
|
email,
|
|
fullName,
|
|
tier,
|
|
investmentAmount,
|
|
position: Number(entry?.position ?? BASE_WAITLIST_OFFSET),
|
|
referralCode,
|
|
totalRegistered: Number(entry?.position ?? BASE_WAITLIST_OFFSET),
|
|
referredBy,
|
|
createdAt: entry?.created_at
|
|
}, 201);
|
|
}
|
|
|
|
async function listEntries(env: Env) {
|
|
const { results } = await env.DB.prepare(
|
|
`SELECT id, email, full_name, tier, investment_amount, position, referral_code, referred_by, created_at
|
|
FROM waitlist_entries ORDER BY created_at DESC`
|
|
).all();
|
|
return json(results);
|
|
}
|
|
|
|
export default {
|
|
async fetch(request: Request, env: Env): Promise<Response> {
|
|
if (request.method === 'OPTIONS') return new Response(null, { headers: CORS_HEADERS });
|
|
|
|
const url = new URL(request.url);
|
|
try {
|
|
if (request.method === 'GET' && url.pathname === '/api/waitlist/stats') {
|
|
return json(await getStats(env));
|
|
}
|
|
if (request.method === 'POST' && url.pathname === '/api/waitlist') {
|
|
return register(request, env);
|
|
}
|
|
if (request.method === 'GET' && url.pathname === '/api/waitlist/entries') {
|
|
return listEntries(env);
|
|
}
|
|
return json({ error: 'Not found' }, 404);
|
|
} catch (error) {
|
|
console.error(error);
|
|
return json({ error: 'Unable to complete the waitlist request.' }, 500);
|
|
}
|
|
}
|
|
};
|