diff --git a/src/app/api/projects/route.ts b/src/app/api/projects/route.ts new file mode 100644 index 0000000..69d4023 --- /dev/null +++ b/src/app/api/projects/route.ts @@ -0,0 +1,89 @@ +import { NextRequest, NextResponse } from "next/server"; +import { prisma } from "@/core/storage/prisma"; + +const NAMESPACE = "tags"; + +// ─── Auth: same Bearer token as address-book ──────────────────────── + +function checkBearerAuth(req: NextRequest): boolean { + const secret = process.env.ADDRESSBOOK_API_KEY; + if (!secret) return false; + const authHeader = req.headers.get("Authorization"); + const token = authHeader?.replace("Bearer ", ""); + return token === secret; +} + +// ─── GET /api/projects ────────────────────────────────────────────── +// Read-only. Returns all tags with category = "project". +// +// Query params: +// ?q= → search in label / projectCode +// ?company= → filter by companyId (beletage, urban-switch, studii-de-teren) +// ?id= → single project by tag ID + +export async function GET(req: NextRequest) { + if (!checkBearerAuth(req)) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const params = req.nextUrl.searchParams; + const id = params.get("id"); + const q = params.get("q")?.toLowerCase(); + const company = params.get("company"); + + try { + // Single project by ID + if (id) { + const item = await prisma.keyValueStore.findUnique({ + where: { namespace_key: { namespace: NAMESPACE, key: id } }, + }); + if (!item) { + return NextResponse.json({ error: "Project not found" }, { status: 404 }); + } + const val = item.value as Record; + if (val.category !== "project") { + return NextResponse.json({ error: "Not a project tag" }, { status: 404 }); + } + return NextResponse.json({ project: val }); + } + + // All project tags + const items = await prisma.keyValueStore.findMany({ + where: { namespace: NAMESPACE }, + select: { key: true, value: true }, + }); + + let projects: Record[] = []; + for (const item of items) { + const val = item.value as Record; + if (!val || val.category !== "project") continue; + + // Company filter + if (company && val.companyId !== company) continue; + + // Search filter + if (q) { + const label = String(val.label ?? "").toLowerCase(); + const code = String(val.projectCode ?? "").toLowerCase(); + if (!label.includes(q) && !code.includes(q)) continue; + } + + projects.push(val); + } + + // Sort by projectCode (B-001, B-002, US-001...) then label + projects.sort((a, b) => { + const aCode = String(a.projectCode ?? ""); + const bCode = String(b.projectCode ?? ""); + if (aCode && bCode) return aCode.localeCompare(bCode); + if (aCode) return -1; + if (bCode) return 1; + return String(a.label ?? "").localeCompare(String(b.label ?? ""), "ro"); + }); + + return NextResponse.json({ projects, total: projects.length }); + } catch (error) { + console.error("Projects GET error:", error); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); + } +} diff --git a/src/middleware.ts b/src/middleware.ts index 6d7ce1c..0157535 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -46,6 +46,6 @@ export const config = { * - /favicon.ico, /robots.txt, /sitemap.xml * - Files with extensions (images, fonts, etc.) */ - "/((?!api/auth|api/notifications/digest|api/compress-pdf|api/address-book|auth/signin|_next|favicon\\.ico|robots\\.txt|sitemap\\.xml|.*\\..*).*)", + "/((?!api/auth|api/notifications/digest|api/compress-pdf|api/address-book|api/projects|auth/signin|_next|favicon\\.ico|robots\\.txt|sitemap\\.xml|.*\\..*).*)", ], };