// server/capabilities.ts
export function canSell(u: User, chef: Chef) {
return u.phoneVerified
&& ageAtLeast(u.dob, 18)
&& !!chef.kitchenGeo
&& chef.sellingStatus === 'enabled'
&& u.legalAcceptedAt !== null;
}
router.use(adminMiddleware) // admin-routes.ts:59
// migrations/20260501000002_create_drop_pickup_slots_and_reservations.js
pgm.createTable('drop_reservations', {
id: 'id', drop_id: { type: 'uuid', references: 'meal_drops' },
status: { type: 'text' } // pending → accepted → ready → collected
});
app.post('/meal-drops/:id/reserve', canReserveDrop, async (req, res) => {
const drop = await drops.decrement(req.params.id);
await notify(drop.cookId, 'reservation.created');
});
// story expiry — every 5 minutes
cron('*/5 * * * *', archiveThenExpire);
const OTP_TTL_MINUTES = 10;
const OTP_MAX_ATTEMPTS = 5;
// TODO: no refund path found beside cancel()
export async function cancelReservation(id) {
await restoreQuantity(id);
// notify cook?
}
OBJECT_STORAGE_BACKEND = process.env.OBJECT_STORAGE_BACKEND ?? 'replit'
resend = new Resend(process.env.RESEND_API_KEY) // key not named in project
GET /explore · GET /chefs/map?lat&lng&radius
hide_profile = false · status = 'approved' · haversine ≤ radius
audit_log(actor_id, target, action, diff_json) // kept after account deletion
// server/capabilities.ts
export function canSell(u: User, chef: Chef) {
return u.phoneVerified
&& ageAtLeast(u.dob, 18)
&& !!chef.kitchenGeo
&& chef.sellingStatus === 'enabled'
&& u.legalAcceptedAt !== null;
}
router.use(adminMiddleware) // admin-routes.ts:59
// migrations/20260501000002_create_drop_pickup_slots_and_reservations.js
pgm.createTable('drop_reservations', {
id: 'id', drop_id: { type: 'uuid', references: 'meal_drops' },
status: { type: 'text' } // pending → accepted → ready → collected
});
app.post('/meal-drops/:id/reserve', canReserveDrop, async (req, res) => {
const drop = await drops.decrement(req.params.id);
await notify(drop.cookId, 'reservation.created');
});
// story expiry — every 5 minutes
cron('*/5 * * * *', archiveThenExpire);
const OTP_TTL_MINUTES = 10;
const OTP_MAX_ATTEMPTS = 5;
// TODO: no refund path found beside cancel()
export async function cancelReservation(id) {
await restoreQuantity(id);
// notify cook?
}
OBJECT_STORAGE_BACKEND = process.env.OBJECT_STORAGE_BACKEND ?? 'replit'
resend = new Resend(process.env.RESEND_API_KEY) // key not named in project
GET /explore · GET /chefs/map?lat&lng&radius
hide_profile = false · status = 'approved' · haversine ≤ radius
audit_log(actor_id, target, action, diff_json) // kept after account deletion
// server/notifications.ts
export async function notify(userId, kind, payload) {
const tokens = await pushTokens(userId);
if (!tokens.length) return; // app closed, no token
await expo.send(tokens, render(kind, payload));
}
app.get('/chefs/map', async (req, res) => {
const { lat, lng, radius } = req.query;
res.json(await chefs.near(lat, lng, radius, { approved: true, hidden: false }));
});
// story_sessions → story_slides → chef_stories
POST /v1/stories/upload-url // clips bypass the API
POST /v1/stories // publish
const ws = setupWebSocket(httpServer); // same process as Express
router.post('/content-reports/:id/resolve', async (req, res) => {
await reports.resolve(req.params.id, req.admin.id);
await createAuditLog(req.admin.id, 'report', 'resolve', diff);
});
// 39 of 48 admin writes call createAuditLog()
expires_at = now() + interval '24 hours' // per story, not fixed
// server/notifications.ts
export async function notify(userId, kind, payload) {
const tokens = await pushTokens(userId);
if (!tokens.length) return; // app closed, no token
await expo.send(tokens, render(kind, payload));
}
app.get('/chefs/map', async (req, res) => {
const { lat, lng, radius } = req.query;
res.json(await chefs.near(lat, lng, radius, { approved: true, hidden: false }));
});
// story_sessions → story_slides → chef_stories
POST /v1/stories/upload-url // clips bypass the API
POST /v1/stories // publish
const ws = setupWebSocket(httpServer); // same process as Express
router.post('/content-reports/:id/resolve', async (req, res) => {
await reports.resolve(req.params.id, req.admin.id);
await createAuditLog(req.admin.id, 'report', 'resolve', diff);
});
// 39 of 48 admin writes call createAuditLog()
expires_at = now() + interval '24 hours' // per story, not fixed
// migrations/20260430000006_role_to_user_admin_only.js
pgm.sql("UPDATE users SET role = 'user' WHERE role = 'chef'");
// cook is a capability, not a role
canPostStory = (u) => u.phoneVerified && chef(u).status === 'approved'
canRequestFood = (u) => u.phoneVerified && identityComplete(u)
GET /api/health // ten probes
db · twilio · resend · storage · stream · push · jobs · migrations · ai · websockets
quantity_remaining = quantity_remaining - 1 // never below zero
on decline | cancel → quantity_remaining + 1
// no payments table in 31 migrations
// no stripe · paypal · square · braintree · adyen in 109 packages
app.post('/api/generate-image', async (req, res) => { // replit_integrations/ — no guard found
const OPEN_ROUTES = 46 // 36 read · 8 write · 2 not read
reliability.score(cookId) // collections count; no-shows do not exist as a state
// migrations/20260430000006_role_to_user_admin_only.js
pgm.sql("UPDATE users SET role = 'user' WHERE role = 'chef'");
// cook is a capability, not a role
canPostStory = (u) => u.phoneVerified && chef(u).status === 'approved'
canRequestFood = (u) => u.phoneVerified && identityComplete(u)
GET /api/health // ten probes
db · twilio · resend · storage · stream · push · jobs · migrations · ai · websockets
quantity_remaining = quantity_remaining - 1 // never below zero
on decline | cancel → quantity_remaining + 1
// no payments table in 31 migrations
// no stripe · paypal · square · braintree · adyen in 109 packages
app.post('/api/generate-image', async (req, res) => { // replit_integrations/ — no guard found
const OPEN_ROUTES = 46 // 36 read · 8 write · 2 not read
reliability.score(cookId) // collections count; no-shows do not exist as a state