Files
ProjectE/apps/web/app/api/reports/route.ts
T

113 lines
3.8 KiB
TypeScript
Raw Normal View History

import { NextRequest, NextResponse } from 'next/server';
import { withAuth, createErrorResponse } from '@/lib/auth';
import { db, reports } from '@project-e/db';
import { and, asc, desc, eq, ilike, or, sql } from 'drizzle-orm';
import { z } from 'zod';
// Map Drizzle DB fields to frontend-expected field names
function mapReport(report: Record<string, any>) {
return {
id: report.id,
title: report.title,
content: report.content || '',
report_type: report.reportType || report.report_type || 'custom',
date_range_start: report.dateRangeStart?.toISOString?.() || report.date_range_start || null,
date_range_end: report.dateRangeEnd?.toISOString?.() || report.date_range_end || null,
domain: report.domain || 'personal',
is_draft: report.isDraft ?? report.is_draft ?? false,
created: report.createdAt?.toISOString?.() || report.created || new Date().toISOString(),
updated: report.updatedAt?.toISOString?.() || report.updated || new Date().toISOString(),
};
}
const createReportSchema = z.object({
title: z.string().min(1, 'Title is required').default('Untitled report'),
content: z.string().optional().default(''),
report_type: z.enum(['weekly', 'monthly', 'project', 'habit', 'custom']).default('custom'),
date_range_start: z.string().optional(),
date_range_end: z.string().optional(),
domain: z.string().default('personal'),
});
// GET /api/reports — List reports
export const GET = withAuth(async (request: NextRequest, _user) => {
const { searchParams } = new URL(request.url);
const page = Math.max(1, parseInt(searchParams.get('page') || '1'));
const perPage = Math.min(100, Math.max(1, parseInt(searchParams.get('perPage') || '50')));
const sortParam = searchParams.get('sort') || '-created';
const filter = searchParams.get('filter') || undefined;
const sortDir = sortParam.startsWith('-') ? 'desc' : 'asc';
const sortField = sortParam.replace(/^-/, '');
const sortColumns: Record<string, any> = {
title: reports.title,
report_type: reports.reportType,
domain: reports.domain,
created: reports.createdAt,
updated: reports.updatedAt,
};
const orderBy = sortDir === 'asc'
? asc(sortColumns[sortField] || reports.createdAt)
: desc(sortColumns[sortField] || reports.createdAt);
const conditions: any[] = [];
if (filter) {
conditions.push(
or(
ilike(reports.title, `%${filter}%`),
)!
);
}
const offset = (page - 1) * perPage;
const [items, countResult] = await Promise.all([
db.select()
.from(reports)
.where(and(...conditions))
.orderBy(orderBy)
.limit(perPage)
.offset(offset),
db.select({ count: sql<number>`count(*)` })
.from(reports)
.where(and(...conditions)),
]);
const totalItems = Number(countResult[0]?.count || 0);
return NextResponse.json({
items: items.map(mapReport),
totalItems,
totalPages: Math.ceil(totalItems / perPage),
page,
perPage,
});
});
// POST /api/reports — Create a report
export const POST = withAuth(async (request: NextRequest, _user) => {
try {
const body = await request.json();
const data = createReportSchema.parse(body);
const [report] = await db.insert(reports)
.values({
title: data.title,
content: data.content || '',
reportType: data.report_type,
dateRangeStart: data.date_range_start ? new Date(data.date_range_start) : null,
dateRangeEnd: data.date_range_end ? new Date(data.date_range_end) : null,
domain: data.domain,
})
.returning();
return NextResponse.json(mapReport(report), { status: 201 });
} catch (error) {
if (error instanceof z.ZodError) {
return createErrorResponse('VALIDATION_ERROR', 'Invalid input', 400, error.issues);
}
throw error;
}
});