diff --git a/server/src/db/database.ts b/server/src/db/database.ts index 58f76c1a..0b59233d 100644 --- a/server/src/db/database.ts +++ b/server/src/db/database.ts @@ -128,9 +128,4 @@ function isOwner(tripId: number | string, userId: number): boolean { return !!_db!.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId); } -function getTripOwnerId(tripId: number | string): number | undefined { - const row = _db!.prepare('SELECT user_id FROM trips WHERE id = ?').get(tripId) as { user_id: number } | undefined; - return row?.user_id; -} - -export { db, closeDb, reinitialize, getPlaceWithTags, canAccessTrip, isOwner, getTripOwnerId }; +export { db, closeDb, reinitialize, getPlaceWithTags, canAccessTrip, isOwner }; diff --git a/server/src/routes/budget.ts b/server/src/routes/budget.ts index 621b958a..9befbc48 100644 --- a/server/src/routes/budget.ts +++ b/server/src/routes/budget.ts @@ -1,5 +1,5 @@ import express, { Request, Response } from 'express'; -import { db, canAccessTrip, getTripOwnerId } from '../db/database'; +import { db, canAccessTrip } from '../db/database'; import { authenticate } from '../middleware/auth'; import { broadcast } from '../websocket'; import { checkPermission } from '../services/permissions'; @@ -84,9 +84,7 @@ router.post('/', authenticate, (req: Request, res: Response) => { const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('budget_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('budget_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); if (!name) return res.status(400).json({ error: 'Name is required' }); @@ -121,9 +119,7 @@ router.put('/:id', authenticate, (req: Request, res: Response) => { const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('budget_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('budget_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const item = db.prepare('SELECT * FROM budget_items WHERE id = ? AND trip_id = ?').get(id, tripId); @@ -159,11 +155,10 @@ router.put('/:id', authenticate, (req: Request, res: Response) => { router.put('/:id/members', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, id } = req.params; - if (!canAccessTrip(Number(tripId), authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); + const access = canAccessTrip(Number(tripId), authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('budget_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('budget_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const item = db.prepare('SELECT * FROM budget_items WHERE id = ? AND trip_id = ?').get(id, tripId); @@ -194,11 +189,10 @@ router.put('/:id/members', authenticate, (req: Request, res: Response) => { router.put('/:id/members/:userId/paid', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, id, userId } = req.params; - if (!canAccessTrip(Number(tripId), authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); + const access = canAccessTrip(Number(tripId), authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('budget_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('budget_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const { paid } = req.body; @@ -294,9 +288,7 @@ router.delete('/:id', authenticate, (req: Request, res: Response) => { const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('budget_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('budget_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const item = db.prepare('SELECT id FROM budget_items WHERE id = ? AND trip_id = ?').get(id, tripId); diff --git a/server/src/routes/collab.ts b/server/src/routes/collab.ts index a51a23bf..18e51b6a 100644 --- a/server/src/routes/collab.ts +++ b/server/src/routes/collab.ts @@ -3,7 +3,7 @@ import multer from 'multer'; import path from 'path'; import fs from 'fs'; import { v4 as uuidv4 } from 'uuid'; -import { db, canAccessTrip, getTripOwnerId } from '../db/database'; +import { db, canAccessTrip } from '../db/database'; import { authenticate } from '../middleware/auth'; import { broadcast } from '../websocket'; import { validateStringLengths } from '../middleware/validate'; @@ -112,10 +112,9 @@ router.post('/notes', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId } = req.params; const { title, content, category, color, website } = req.body; - if (!verifyTripAccess(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('collab_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyTripAccess(tripId, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('collab_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); if (!title) return res.status(400).json({ error: 'Title is required' }); @@ -142,10 +141,9 @@ router.put('/notes/:id', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, id } = req.params; const { title, content, category, color, pinned, website } = req.body; - if (!verifyTripAccess(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('collab_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyTripAccess(tripId, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('collab_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const existing = db.prepare('SELECT * FROM collab_notes WHERE id = ? AND trip_id = ?').get(id, tripId); @@ -183,10 +181,9 @@ router.put('/notes/:id', authenticate, (req: Request, res: Response) => { router.delete('/notes/:id', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, id } = req.params; - if (!verifyTripAccess(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('collab_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyTripAccess(tripId, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('collab_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const existing = db.prepare('SELECT id FROM collab_notes WHERE id = ? AND trip_id = ?').get(id, tripId); @@ -207,10 +204,9 @@ router.delete('/notes/:id', authenticate, (req: Request, res: Response) => { router.post('/notes/:id/files', authenticate, noteUpload.single('file'), (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, id } = req.params; - if (!verifyTripAccess(Number(tripId), authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('collab_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyTripAccess(Number(tripId), authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('collab_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); if (!req.file) return res.status(400).json({ error: 'No file uploaded' }); @@ -229,10 +225,9 @@ router.post('/notes/:id/files', authenticate, noteUpload.single('file'), (req: R router.delete('/notes/:id/files/:fileId', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, id, fileId } = req.params; - if (!verifyTripAccess(Number(tripId), authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('collab_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyTripAccess(Number(tripId), authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('collab_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const file = db.prepare('SELECT * FROM trip_files WHERE id = ? AND note_id = ?').get(fileId, id) as TripFile | undefined; @@ -298,10 +293,9 @@ router.post('/polls', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId } = req.params; const { question, options, multiple, multiple_choice, deadline } = req.body; - if (!verifyTripAccess(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('collab_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyTripAccess(tripId, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('collab_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); if (!question) return res.status(400).json({ error: 'Question is required' }); if (!Array.isArray(options) || options.length < 2) { @@ -324,10 +318,9 @@ router.post('/polls/:id/vote', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, id } = req.params; const { option_index } = req.body; - if (!verifyTripAccess(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('collab_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyTripAccess(tripId, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('collab_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const poll = db.prepare('SELECT * FROM collab_polls WHERE id = ? AND trip_id = ?').get(id, tripId) as CollabPoll | undefined; @@ -360,10 +353,9 @@ router.post('/polls/:id/vote', authenticate, (req: Request, res: Response) => { router.put('/polls/:id/close', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, id } = req.params; - if (!verifyTripAccess(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('collab_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyTripAccess(tripId, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('collab_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const poll = db.prepare('SELECT * FROM collab_polls WHERE id = ? AND trip_id = ?').get(id, tripId); @@ -379,10 +371,9 @@ router.put('/polls/:id/close', authenticate, (req: Request, res: Response) => { router.delete('/polls/:id', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, id } = req.params; - if (!verifyTripAccess(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('collab_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyTripAccess(tripId, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('collab_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const poll = db.prepare('SELECT id FROM collab_polls WHERE id = ? AND trip_id = ?').get(id, tripId); @@ -437,10 +428,9 @@ router.post('/messages', authenticate, validateStringLengths({ text: 5000 }), (r const authReq = req as AuthRequest; const { tripId } = req.params; const { text, reply_to } = req.body; - if (!verifyTripAccess(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('collab_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyTripAccess(tripId, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('collab_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); if (!text || !text.trim()) return res.status(400).json({ error: 'Message text is required' }); @@ -479,10 +469,9 @@ router.post('/messages/:id/react', authenticate, (req: Request, res: Response) = const authReq = req as AuthRequest; const { tripId, id } = req.params; const { emoji } = req.body; - if (!verifyTripAccess(Number(tripId), authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('collab_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyTripAccess(Number(tripId), authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('collab_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); if (!emoji) return res.status(400).json({ error: 'Emoji is required' }); @@ -504,10 +493,9 @@ router.post('/messages/:id/react', authenticate, (req: Request, res: Response) = router.delete('/messages/:id', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, id } = req.params; - if (!verifyTripAccess(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('collab_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyTripAccess(tripId, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('collab_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const message = db.prepare('SELECT * FROM collab_messages WHERE id = ? AND trip_id = ?').get(id, tripId) as CollabMessage | undefined; diff --git a/server/src/routes/dayNotes.ts b/server/src/routes/dayNotes.ts index bcba7829..7c60f3f9 100644 --- a/server/src/routes/dayNotes.ts +++ b/server/src/routes/dayNotes.ts @@ -1,5 +1,5 @@ import express, { Request, Response } from 'express'; -import { db, canAccessTrip, getTripOwnerId } from '../db/database'; +import { db, canAccessTrip } from '../db/database'; import { authenticate } from '../middleware/auth'; import { broadcast } from '../websocket'; import { validateStringLengths } from '../middleware/validate'; @@ -27,11 +27,9 @@ router.get('/', authenticate, (req: Request, res: Response) => { router.post('/', authenticate, validateStringLengths({ text: 500, time: 150 }), (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, dayId } = req.params; - if (!verifyAccess(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('day_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyAccess(tripId, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('day_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const day = db.prepare('SELECT id FROM days WHERE id = ? AND trip_id = ?').get(dayId, tripId); @@ -52,11 +50,9 @@ router.post('/', authenticate, validateStringLengths({ text: 500, time: 150 }), router.put('/:id', authenticate, validateStringLengths({ text: 500, time: 150 }), (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, dayId, id } = req.params; - if (!verifyAccess(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('day_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyAccess(tripId, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('day_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const note = db.prepare('SELECT * FROM day_notes WHERE id = ? AND day_id = ? AND trip_id = ?').get(id, dayId, tripId) as DayNote | undefined; @@ -81,11 +77,9 @@ router.put('/:id', authenticate, validateStringLengths({ text: 500, time: 150 }) router.delete('/:id', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, dayId, id } = req.params; - if (!verifyAccess(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('day_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = verifyAccess(tripId, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('day_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const note = db.prepare('SELECT id FROM day_notes WHERE id = ? AND day_id = ? AND trip_id = ?').get(id, dayId, tripId); diff --git a/server/src/routes/packing.ts b/server/src/routes/packing.ts index aceb8550..bf325ab8 100644 --- a/server/src/routes/packing.ts +++ b/server/src/routes/packing.ts @@ -1,5 +1,5 @@ import express, { Request, Response } from 'express'; -import { db, canAccessTrip, getTripOwnerId } from '../db/database'; +import { db, canAccessTrip } from '../db/database'; import { authenticate } from '../middleware/auth'; import { broadcast } from '../websocket'; import { checkPermission } from '../services/permissions'; @@ -34,9 +34,7 @@ router.post('/import', authenticate, (req: Request, res: Response) => { const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('packing_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('packing_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); if (!Array.isArray(items) || items.length === 0) return res.status(400).json({ error: 'items must be a non-empty array' }); @@ -85,9 +83,7 @@ router.post('/', authenticate, (req: Request, res: Response) => { const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('packing_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('packing_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); if (!name) return res.status(400).json({ error: 'Item name is required' }); @@ -112,9 +108,7 @@ router.put('/:id', authenticate, (req: Request, res: Response) => { const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('packing_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('packing_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const item = db.prepare('SELECT * FROM packing_items WHERE id = ? AND trip_id = ?').get(id, tripId); @@ -152,9 +146,7 @@ router.delete('/:id', authenticate, (req: Request, res: Response) => { const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('packing_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('packing_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const item = db.prepare('SELECT id FROM packing_items WHERE id = ? AND trip_id = ?').get(id, tripId); @@ -182,9 +174,7 @@ router.post('/bags', authenticate, (req: Request, res: Response) => { const { name, color } = req.body; const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('packing_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('packing_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); if (!name?.trim()) return res.status(400).json({ error: 'Name is required' }); const maxOrder = db.prepare('SELECT MAX(sort_order) as max FROM packing_bags WHERE trip_id = ?').get(tripId) as { max: number | null }; @@ -200,9 +190,7 @@ router.put('/bags/:bagId', authenticate, (req: Request, res: Response) => { const { name, color, weight_limit_grams } = req.body; const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('packing_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('packing_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const bag = db.prepare('SELECT * FROM packing_bags WHERE id = ? AND trip_id = ?').get(bagId, tripId); if (!bag) return res.status(404).json({ error: 'Bag not found' }); @@ -217,9 +205,7 @@ router.delete('/bags/:bagId', authenticate, (req: Request, res: Response) => { const { tripId, bagId } = req.params; const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('packing_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('packing_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const bag = db.prepare('SELECT * FROM packing_bags WHERE id = ? AND trip_id = ?').get(bagId, tripId); if (!bag) return res.status(404).json({ error: 'Bag not found' }); @@ -237,9 +223,7 @@ router.post('/apply-template/:templateId', authenticate, (req: Request, res: Res const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('packing_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('packing_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const templateItems = db.prepare(` @@ -299,9 +283,7 @@ router.put('/category-assignees/:categoryName', authenticate, (req: Request, res const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('packing_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('packing_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const cat = decodeURIComponent(categoryName); @@ -343,9 +325,7 @@ router.put('/reorder', authenticate, (req: Request, res: Response) => { const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('packing_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('packing_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const update = db.prepare('UPDATE packing_items SET sort_order = ? WHERE id = ? AND trip_id = ?'); diff --git a/server/src/routes/reservations.ts b/server/src/routes/reservations.ts index 215d8914..f2df0317 100644 --- a/server/src/routes/reservations.ts +++ b/server/src/routes/reservations.ts @@ -1,5 +1,5 @@ import express, { Request, Response } from 'express'; -import { db, canAccessTrip, getTripOwnerId } from '../db/database'; +import { db, canAccessTrip } from '../db/database'; import { authenticate } from '../middleware/auth'; import { broadcast } from '../websocket'; import { checkPermission } from '../services/permissions'; @@ -41,9 +41,7 @@ router.post('/', authenticate, (req: Request, res: Response) => { const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('reservation_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('reservation_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); if (!title) return res.status(400).json({ error: 'Title is required' }); @@ -124,9 +122,7 @@ router.put('/positions', authenticate, (req: Request, res: Response) => { const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('reservation_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('reservation_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); if (!Array.isArray(positions)) return res.status(400).json({ error: 'positions must be an array' }); @@ -151,9 +147,7 @@ router.put('/:id', authenticate, (req: Request, res: Response) => { const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('reservation_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('reservation_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const reservation = db.prepare('SELECT * FROM reservations WHERE id = ? AND trip_id = ?').get(id, tripId) as Reservation | undefined; @@ -252,9 +246,7 @@ router.delete('/:id', authenticate, (req: Request, res: Response) => { const trip = verifyTripOwnership(tripId, authReq.user.id); if (!trip) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('reservation_edit', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + if (!checkPermission('reservation_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const reservation = db.prepare('SELECT id, title, type, accommodation_id FROM reservations WHERE id = ? AND trip_id = ?').get(id, tripId) as { id: number; title: string; type: string; accommodation_id: number | null } | undefined; diff --git a/server/src/routes/share.ts b/server/src/routes/share.ts index 2f6652b1..87f962a2 100644 --- a/server/src/routes/share.ts +++ b/server/src/routes/share.ts @@ -1,6 +1,6 @@ import express, { Request, Response } from 'express'; import crypto from 'crypto'; -import { db, canAccessTrip, getTripOwnerId } from '../db/database'; +import { db, canAccessTrip } from '../db/database'; import { authenticate } from '../middleware/auth'; import { checkPermission } from '../services/permissions'; import { AuthRequest } from '../types'; @@ -12,10 +12,9 @@ const router = express.Router(); router.post('/trips/:tripId/share-link', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId } = req.params; - if (!canAccessTrip(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('share_manage', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = canAccessTrip(tripId, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('share_manage', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); const { share_map = true, share_bookings = true, share_packing = false, share_budget = false, share_collab = false } = req.body || {}; @@ -49,10 +48,9 @@ router.get('/trips/:tripId/share-link', authenticate, (req: Request, res: Respon router.delete('/trips/:tripId/share-link', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId } = req.params; - if (!canAccessTrip(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(tripId); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - if (!checkPermission('share_manage', authReq.user.role, tripOwnerId, authReq.user.id, tripOwnerId !== authReq.user.id)) + const access = canAccessTrip(tripId, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + if (!checkPermission('share_manage', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id)) return res.status(403).json({ error: 'No permission' }); db.prepare('DELETE FROM share_tokens WHERE trip_id = ?').run(tripId); diff --git a/server/src/routes/trips.ts b/server/src/routes/trips.ts index b35ee031..07a130b7 100644 --- a/server/src/routes/trips.ts +++ b/server/src/routes/trips.ts @@ -3,7 +3,7 @@ import multer from 'multer'; import path from 'path'; import fs from 'fs'; import { v4 as uuidv4 } from 'uuid'; -import { db, canAccessTrip, getTripOwnerId } from '../db/database'; +import { db, canAccessTrip } from '../db/database'; import { authenticate, demoUploadBlock } from '../middleware/auth'; import { broadcast } from '../websocket'; import { AuthRequest, Trip, User } from '../types'; @@ -264,9 +264,10 @@ router.put('/:id', authenticate, (req: Request, res: Response) => { router.post('/:id/cover', authenticate, demoUploadBlock, uploadCover.single('cover'), (req: Request, res: Response) => { const authReq = req as AuthRequest; - const tripOwnerId = getTripOwnerId(req.params.id); + const access = canAccessTrip(req.params.id, authReq.user.id); + const tripOwnerId = access?.user_id; if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); - const isMember = tripOwnerId !== authReq.user.id && !!canAccessTrip(req.params.id, authReq.user.id); + const isMember = tripOwnerId !== authReq.user.id; if (!checkPermission('trip_cover_upload', authReq.user.role, tripOwnerId, authReq.user.id, isMember)) return res.status(403).json({ error: 'No permission to change the cover image' }); @@ -290,8 +291,9 @@ router.post('/:id/cover', authenticate, demoUploadBlock, uploadCover.single('cov router.delete('/:id', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; - const tripOwnerId = getTripOwnerId(req.params.id); - if (!tripOwnerId) return res.status(404).json({ error: 'Trip not found' }); + const trip = db.prepare('SELECT user_id FROM trips WHERE id = ?').get(req.params.id) as { user_id: number } | undefined; + if (!trip) return res.status(404).json({ error: 'Trip not found' }); + const tripOwnerId = trip.user_id; if (!checkPermission('trip_delete', authReq.user.role, tripOwnerId, authReq.user.id, false)) return res.status(403).json({ error: 'No permission to delete this trip' }); const deletedTripId = Number(req.params.id); @@ -309,10 +311,11 @@ router.delete('/:id', authenticate, (req: Request, res: Response) => { router.get('/:id/members', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; - if (!canAccessTrip(req.params.id, authReq.user.id)) + const access = canAccessTrip(req.params.id, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(req.params.id)!; + const tripOwnerId = access.user_id; const members = db.prepare(` SELECT u.id, u.username, u.email, u.avatar, CASE WHEN u.id = ? THEN 'owner' ELSE 'member' END as role, @@ -336,10 +339,11 @@ router.get('/:id/members', authenticate, (req: Request, res: Response) => { router.post('/:id/members', authenticate, (req: Request, res: Response) => { const authReq = req as AuthRequest; - if (!canAccessTrip(req.params.id, authReq.user.id)) + const access = canAccessTrip(req.params.id, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); - const tripOwnerId = getTripOwnerId(req.params.id)!; + const tripOwnerId = access.user_id; const isMember = tripOwnerId !== authReq.user.id; if (!checkPermission('member_manage', authReq.user.role, tripOwnerId, authReq.user.id, isMember)) return res.status(403).json({ error: 'No permission to manage members' }); @@ -378,9 +382,10 @@ router.delete('/:id/members/:userId', authenticate, (req: Request, res: Response const targetId = parseInt(req.params.userId); const isSelf = targetId === authReq.user.id; if (!isSelf) { - const tripOwnerId = getTripOwnerId(req.params.id)!; - const memberCheck = tripOwnerId !== authReq.user.id; - if (!checkPermission('member_manage', authReq.user.role, tripOwnerId, authReq.user.id, memberCheck)) + const access = canAccessTrip(req.params.id, authReq.user.id); + if (!access) return res.status(404).json({ error: 'Trip not found' }); + const memberCheck = access.user_id !== authReq.user.id; + if (!checkPermission('member_manage', authReq.user.role, access.user_id, authReq.user.id, memberCheck)) return res.status(403).json({ error: 'No permission to remove members' }); }