Deploy: Add Cloudflare Worker, D1 database integration, and updated README
- 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
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
name = "bm-connect-api"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2026-08-26"
|
||||
|
||||
[[d1_databases]]
|
||||
binding = "DB"
|
||||
database_name = "bm_connect_db"
|
||||
database_id = "5cf8ba8c-fe38-4db4-bf95-aa8fb8c09892"
|
||||
Reference in New Issue
Block a user