Code Examples
Code patterns used in Danvas development
Danvas uses several key patterns for server actions, database queries, and API integrations.
Server Actions
Danvas uses Next.js server actions for form submissions and data mutations.
Basic Form Submission
// apps/app/app/(authenticated)/forms/actions.ts
'use server'
export async function submitForm(formId: string, data: FormData) {
const { userId, teamId } = await requireUserAuth();
const validated = submitFormSchema.parse({ formId, ...Object.fromEntries(data) });
const submission = await db.insert(formSubmissions).values({
id: generateId(),
formId: validated.formId,
userId,
teamId,
data: validated,
submittedAt: new Date(),
});
await logAudit({ teamId, userId, action: 'form.submitted', targetId: submission.id });
return submission;
}Database Insert with Audit Log
// Using the audit helper
import { logAudit } from '@repo/database/audit';
await db.insert(incidents).values({
id: generateId(),
teamId,
userId,
locationId,
type,
severity,
notes,
createdAt: new Date(),
});
await logAudit({ teamId, userId, action: 'incident.created', targetId: incidentId });Database Queries
Query with Filters
// Get submissions with optional filters
const submissions = await db.query.formSubmissions.findMany({
where: and(
eq(formSubmissions.teamId, teamId),
optional(formId ? eq(formSubmissions.formId, formId) : undefined),
optional(locationId ? eq(formSubmissions.locationId, locationId) : undefined),
),
with: {
form: true,
location: true,
user: true,
},
orderBy: desc(formSubmissions.submittedAt),
limit: 50,
});Upsert Pattern
// Push subscription upsert
await db.insert(pushSubscriptions).values({
userId,
endpoint: subscription.endpoint,
p256dh: subscription.keys.p256dh,
auth: subscription.keys.auth,
}).onConflictDoUpdate({
target: [pushSubscriptions.userId, pushSubscriptions.endpoint],
set: {
p256dh: subscription.keys.p256dh,
auth: subscription.keys.auth,
},
});Clerk Authentication
Require User Auth
// packages/auth/index.ts
export async function requireUserAuth() {
const { userId, teamId } = await auth().catch(() => ({ userId: null, teamId: null }));
if (!userId) throw new Error('Unauthorized');
return { userId, teamId };
}Require Admin
export async function requireAdmin() {
const { userId, teamId } = await requireUserAuth();
const user = await db.query.users.findFirst({
where: and(eq(users.clerkId, userId), eq(users.teamId, teamId)),
});
if (user?.role !== 'admin') throw new Error('Forbidden');
return { userId, teamId };
}Zod Schemas
Form Validation
// apps/app/app/(authenticated)/forms/schemas.ts
import { z } from 'zod';
export const submitFormSchema = z.object({
formId: z.string().min(1),
locationId: z.string().min(1),
data: z.record(z.string(), z.any()),
});
export const incidentSchema = z.object({
type: z.enum(['Injury', 'Near Miss', 'Spill / Slip Hazard', 'Complaint', 'Maintenance', 'Stockout']),
severity: z.enum(['low', 'medium', 'high', 'critical']),
date: z.string(),
shift: z.enum(['Breakfast', 'Lunch', 'Dinner', 'Late Night', 'Brunch']),
notes: z.string().max(5000),
mediaUrls: z.array(z.string()).optional(),
});Slack Notifications
Build Block Kit Message
// apps/slack-bot/lib/templates/report.ts
export function buildReportBlocks(report: Report, location: Location) {
return [
{ type: 'header', text: { type: 'plain_text', text: `${location.name} — Shift Report` } },
{ type: 'section', fields: [
{ type: 'mrkdwn', text: `*Sales:* $${report.actualSales} / $${report.goalSales}` },
{ type: 'mrkdwn', text: `*Ratings:* ⭐${report.fohRating} ⭐${report.bohRating}` },
]},
{ type: 'context', elements: [{ type: 'mrkdwn', text: `Submitted via Danvas` }] },
];
}Testing
Server Action Test
// __tests__/reports.test.ts
import { submitReport } from '../actions';
test('submitReport creates report and updates compliance', async () => {
const mockUser = { userId: 'user_123', teamId: 'team_abc' };
const result = await submitReport({ ...mockReport, userId: mockUser.userId });
expect(result.id).toBeDefined();
const compliance = await db.query.compliance.findFirst({
where: eq(compliance.reportId, result.id),
});
expect(compliance.filed).toBe(true);
});Environment Variables
Required for Development
# apps/app/.env.local
DATABASE_URL=postgresql://user:password@ep-xxx.neon.tech/neondb?sslmode=require
CLERK_SECRET_KEY=sk_test_...
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
AI_GATEWAY_API_KEY=<your-vercel-ai-gateway-key>
CANVAS_TEAM_ID=org_...