From 286439cf2c21510739f89ccb34e67008a8dba1f8 Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:20:06 +0330 Subject: [PATCH] Import source: app/backend/src/routes/directory Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- app/backend/src/routes/directory.ts | 84 +++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 app/backend/src/routes/directory.ts diff --git a/app/backend/src/routes/directory.ts b/app/backend/src/routes/directory.ts new file mode 100644 index 0000000..e549757 --- /dev/null +++ b/app/backend/src/routes/directory.ts @@ -0,0 +1,84 @@ +import type { FastifyPluginAsync } from 'fastify'; +import { z } from 'zod'; +import { prisma } from '../db.js'; +import { requireAuth } from '../plugins/session.js'; + +export const directoryRoutes: FastifyPluginAsync = async (app) => { + // Public route to list companies + app.get('/api/v1/directory/companies', async (req) => { + const companies = await prisma.companyProfile.findMany({ + orderBy: { name: 'asc' }, + include: { claims: { where: { status: 'approved' }, include: { user: { select: { name: true } } } } } + }); + return { companies }; + }); + + app.get('/api/v1/directory/companies/:slug', async (req, reply) => { + const { slug } = req.params as { slug: string }; + const company = await prisma.companyProfile.findUnique({ + where: { slug }, + include: { claims: { where: { status: 'approved' }, include: { user: { select: { name: true, username: true } } } } } + }); + if (!company) return reply.code(404).send({ error: 'not_found' }); + return { company }; + }); + + // Authenticated route to claim a company + app.register(async (authApp) => { + authApp.addHook('preHandler', requireAuth()); + + authApp.post('/api/v1/directory/companies/:companyId/claim', async (req, reply) => { + const { companyId } = req.params as { companyId: string }; + const schema = z.object({ documentsUrl: z.string().url().optional() }); + const parsed = schema.safeParse(req.body); + if (!parsed.success) return reply.code(400).send({ error: 'invalid_input' }); + + const company = await prisma.companyProfile.findUnique({ where: { id: companyId } }); + if (!company) return reply.code(404).send({ error: 'not_found' }); + + const claim = await prisma.companyClaim.create({ + data: { + companyId, + userId: req.session!.userId, + documentsUrl: parsed.data.documentsUrl, + } + }); + + return { success: true, claim }; + }); + }); + + // Admin routes + app.register(async (adminApp) => { + adminApp.addHook('preHandler', async (req, reply) => { + if (req.session?.role !== 'admin' && req.session?.role !== 'editor') { + return reply.code(403).send({ error: 'forbidden' }); + } + }); + + adminApp.get('/api/v1/admin/directory/claims', async () => { + const claims = await prisma.companyClaim.findMany({ + where: { status: 'pending' }, + include: { company: true, user: { select: { name: true, email: true } } }, + orderBy: { createdAt: 'desc' } + }); + return { claims }; + }); + + adminApp.patch('/api/v1/admin/directory/claims/:id', async (req, reply) => { + const { id } = req.params as { id: string }; + const parsed = z.object({ status: z.enum(['approved', 'rejected']) }).safeParse(req.body); + if (!parsed.success) return reply.code(400).send({ error: 'invalid_input' }); + + const claim = await prisma.companyClaim.update({ + where: { id }, + data: { + status: parsed.data.status, + reviewedById: req.session!.userId + } + }); + + return { success: true, claim }; + }); + }); +};