import { NextResponse } from "next/server" import { and, desc, eq } from "drizzle-orm" import { db } from "@/lib/db" import { documents, properties } from "@/lib/db/schema" import { getSessionUser } from "@/lib/session" import { saveFile, isAllowedUploadExt, StorageNotConfiguredError } from "@/lib/storage" import { checkStorageLimit } from "@/lib/plan-limits" import { ownsProperty, ownsTenant } from "@/lib/db/ownership" import { getEffectiveOwnerId, getAccountContext } from "@/lib/account" export async function GET(request: Request) { const user = await getSessionUser() if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) const ownerId = await getEffectiveOwnerId(user.id) const { searchParams } = new URL(request.url) const propertyId = searchParams.get("property_id") let propertyName = "" if (propertyId) { const prop = await db.query.properties.findFirst({ where: and(eq(properties.id, propertyId), eq(properties.user_id, ownerId)), columns: { name: true }, }) propertyName = prop?.name ?? "" } const data = await db.query.documents.findMany({ where: and( eq(documents.user_id, ownerId), propertyId ? eq(documents.property_id, propertyId) : undefined ), orderBy: desc(documents.created_at), }) return NextResponse.json({ documents: data, propertyName }) } export async function POST(request: Request) { const user = await getSessionUser() if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) const ctx = await getAccountContext(user.id) const ownerId = ctx.ownerId if (!ctx.canWrite) return NextResponse.json({ error: "Forbidden" }, { status: 403 }) const contentType = request.headers.get("content-type") ?? "" if (contentType.includes("multipart/form-data")) { const fd = await request.formData() const file = fd.get("file") as File | null const propertyId = fd.get("property_id") as string const name = fd.get("name") as string const category = ((fd.get("category") as string) || "other") as typeof documents.$inferInsert.category if (!file) return NextResponse.json({ error: "No file provided" }, { status: 400 }) if (file.size > 20 * 1024 * 1024) return NextResponse.json({ error: "File too large (max 20 MB)" }, { status: 400 }) if (!isAllowedUploadExt(file.name)) return NextResponse.json({ error: "File type not allowed" }, { status: 400 }) const storageError = await checkStorageLimit(ownerId, file.size) if (storageError) return NextResponse.json({ error: storageError }, { status: 403 }) // Verify the property belongs to the user before attaching a document to it. const prop = await db.query.properties.findFirst({ where: and(eq(properties.id, propertyId), eq(properties.user_id, ownerId)), columns: { id: true }, }) if (!prop) return NextResponse.json({ error: "Property not found" }, { status: 404 }) let saved try { saved = await saveFile(file, { userId: ownerId, scope: "documents" }) } catch (err) { if (err instanceof StorageNotConfiguredError) { console.error("[documents]", err.message) return NextResponse.json( { error: "File uploads are temporarily unavailable. Please try again later." }, { status: 503 } ) } throw err } const { key, size, type } = saved const [data] = await db .insert(documents) .values({ user_id: ownerId, property_id: propertyId, name: name || file.name, category, file_url: `/api/files/${key}`, storage_path: key, file_type: type, file_size: size, }) .returning() return NextResponse.json(data, { status: 201 }) } // JSON fallback (metadata only) const body = (await request.json()) as Record const propertyId = body.property_id as string | undefined const tenantId = body.tenant_id as string | undefined // Verify the property/tenant belong to the user before attaching a document. if (!(await ownsProperty(ownerId, propertyId))) { return NextResponse.json({ error: "Property not found" }, { status: 404 }) } if (!(await ownsTenant(ownerId, tenantId))) { return NextResponse.json({ error: "Tenant not found" }, { status: 404 }) } // Whitelist insertable columns — never trust client-supplied user_id/id/created_at. const [data] = await db .insert(documents) .values({ user_id: ownerId, property_id: propertyId as string, tenant_id: tenantId, name: body.name as string, category: (body.category as typeof documents.$inferInsert.category) ?? "other", file_url: body.file_url as string, storage_path: body.storage_path as string | undefined, file_type: body.file_type as string | undefined, file_size: body.file_size as number | undefined, }) .returning() return NextResponse.json(data, { status: 201 }) }