/** * Project Shares Routes – List, create, delete project shares */ import type { FastifyInstance } from 'fastify'; import type { DatabaseInterface } from '../database/DatabaseInterface.js'; import type { AuthService } from '../auth/AuthService.js'; import { validateIdParam } from '../utils/validation.js'; function extractToken(request: any): string | null { const auth = request.headers?.authorization; if (auth && auth.startsWith('Bearer ')) return auth.slice(7); return null; } const VALID_PERMISSIONS = ['view', 'edit', 'admin']; export function registerShareRoutes(fastify: FastifyInstance, db: DatabaseInterface, authService: AuthService) { // List shares for a project (owner only) fastify.get('/api/projects/:projectId/shares', async (request, reply) => { const token = extractToken(request); if (!token) return reply.code(401).send({ error: 'Authentication required' }); const user = authService.getUserFromSession(token); if (!user) return reply.code(401).send({ error: 'Invalid or expired session' }); const { projectId } = request.params as { projectId: string }; const projIdErr = validateIdParam(projectId, 'projectId'); if (projIdErr) return reply.code(400).send({ error: projIdErr }); const project = db.getProject(projectId); if (!project) return reply.code(404).send({ error: 'Project not found' }); if (project.owner_id !== user.id) return reply.code(403).send({ error: 'Forbidden' }); return db.listProjectShares(projectId); }); // Create a project share (owner only) fastify.post('/api/projects/:projectId/shares', async (request, reply) => { const token = extractToken(request); if (!token) return reply.code(401).send({ error: 'Authentication required' }); const user = authService.getUserFromSession(token); if (!user) return reply.code(401).send({ error: 'Invalid or expired session' }); const { projectId } = request.params as { projectId: string }; const projIdErr = validateIdParam(projectId, 'projectId'); if (projIdErr) return reply.code(400).send({ error: projIdErr }); const project = db.getProject(projectId); if (!project) return reply.code(404).send({ error: 'Project not found' }); if (project.owner_id !== user.id) return reply.code(403).send({ error: 'Forbidden' }); const body = request.body as { shared_with_email?: string; permission?: string }; if (!body.shared_with_email) return reply.code(400).send({ error: 'shared_with_email is required' }); const permission = VALID_PERMISSIONS.includes(body.permission ?? '') ? body.permission! : 'view'; const share = db.createProjectShare({ project_id: projectId, shared_with_email: body.shared_with_email, shared_by: user.id, permission, }); // Create a notification for the shared user if they exist const sharedUser = db.getUserByEmail(body.shared_with_email); if (sharedUser) { db.createNotification({ user_id: sharedUser.id, type: 'share', title: 'Projekt geteilt', message: `${user.name} hat ein Projekt mit Ihnen geteilt: ${project.name}`, }); } return reply.code(201).send(share); }); // Delete a project share (owner only) fastify.delete('/api/shares/:id', async (request, reply) => { const token = extractToken(request); if (!token) return reply.code(401).send({ error: 'Authentication required' }); const user = authService.getUserFromSession(token); if (!user) return reply.code(401).send({ error: 'Invalid or expired session' }); const { id } = request.params as { id: string }; const idErr = validateIdParam(id, 'id'); if (idErr) return reply.code(400).send({ error: idErr }); const share = db.getProjectShare(id); if (!share) return reply.code(404).send({ error: 'Share not found' }); const project = db.getProject(share.project_id); if (!project) return reply.code(404).send({ error: 'Project not found' }); if (project.owner_id !== user.id) return reply.code(403).send({ error: 'Forbidden' }); const ok = db.deleteProjectShare(id); if (!ok) return reply.code(404).send({ error: 'Share not found' }); return reply.code(204).send(); }); }