fix: SQLite boolean binding, CSS brace, race condition, 0 npm vulns, code-splitting

This commit is contained in:
Leopoldadmin
2026-07-04 16:08:51 +02:00
parent a4371ca3ce
commit 0606dbb501
20 changed files with 1070 additions and 53 deletions
+71
View File
@@ -0,0 +1,71 @@
/**
* Notifications Routes List, create, mark read, delete notifications
*/
import type { FastifyInstance } from 'fastify';
import type { DatabaseInterface } from '../database/DatabaseInterface.js';
import type { AuthService } from '../auth/AuthService.js';
function extractToken(request: any): string | null {
const auth = request.headers?.authorization;
if (auth && auth.startsWith('Bearer ')) return auth.slice(7);
return null;
}
export function registerNotificationRoutes(fastify: FastifyInstance, db: DatabaseInterface, authService: AuthService) {
// List notifications for current user
fastify.get('/api/notifications', 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' });
return db.listNotifications(user.id);
});
// Create notification (only for self)
fastify.post('/api/notifications', 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 body = request.body as { type?: string; title?: string; message?: string };
if (!body.title || !body.message) return reply.code(400).send({ error: 'title and message are required' });
const validTypes = ['info', 'share', 'warning', 'error'];
const type = validTypes.includes(body.type ?? '') ? body.type! : 'info';
return reply.code(201).send(db.createNotification({
user_id: user.id,
type,
title: body.title,
message: body.message,
}));
});
// Mark notification as read (ownership check)
fastify.patch('/api/notifications/:id/read', 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 notif = db.getNotification(id);
if (!notif) return reply.code(404).send({ error: 'Notification not found' });
if (notif.user_id !== user.id) return reply.code(403).send({ error: 'Forbidden' });
const ok = db.markNotificationRead(id);
if (!ok) return reply.code(404).send({ error: 'Notification not found' });
return reply.code(204).send();
});
// Delete notification (ownership check)
fastify.delete('/api/notifications/: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 notif = db.getNotification(id);
if (!notif) return reply.code(404).send({ error: 'Notification not found' });
if (notif.user_id !== user.id) return reply.code(403).send({ error: 'Forbidden' });
const ok = db.deleteNotification(id);
if (!ok) return reply.code(404).send({ error: 'Notification not found' });
return reply.code(204).send();
});
}
+81
View File
@@ -0,0 +1,81 @@
/**
* 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';
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 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 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 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();
});
}