diff --git a/apps/backend/src/__tests__/event.test.ts b/apps/backend/src/__tests__/event.test.ts index 44806af1..a3b89691 100644 --- a/apps/backend/src/__tests__/event.test.ts +++ b/apps/backend/src/__tests__/event.test.ts @@ -1,8 +1,11 @@ +import Fastify from 'fastify'; import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import Fastify, { FastifyInstance } from 'fastify'; -import { PrismaClient } from '@prisma/client'; + import { eventRoutes } from '../routes/event'; +import type { PrismaClient } from '@prisma/client'; +import type { FastifyInstance } from 'fastify'; + // ─── Shared mock data ──────────────────────────────────────────────────────── const MOCK_USER_ID = 'user-uuid-001'; @@ -64,7 +67,7 @@ const prismaMock = { // // This mirrors the real app setup without touching a real DB or real JWT keys. -let mockJwtVerify = vi.fn(); +const mockJwtVerify = vi.fn(); async function buildApp(): Promise { const app = Fastify({ logger: false }); @@ -77,7 +80,14 @@ async function buildApp(): Promise { app.decorateRequest('jwtVerify', function () { return mockJwtVerify(); }); - + app.decorate('authenticate', async function (request, reply) { + try { + const payload = await request.jwtVerify(); + if (payload) { request.user = payload as typeof request.user; } + } catch { + return reply.status(401).send({ error: 'Unauthorized' }); + } + }); // Register with the same prefix used in production (app.ts) so that // tests exercise routes at their real paths — /api/events, /api/events/:slug, etc. await app.register(eventRoutes, { prefix: '/api/events' }); @@ -251,6 +261,7 @@ describe('Events API', () => { it('200 — returns event info with attendee count', async () => { prismaMock.event.findUnique.mockResolvedValue({ ...MOCK_EVENT, + organizer: { username: 'johndoe', displayName: 'John Doe' }, _count: { attendees: 42 }, }); @@ -258,7 +269,7 @@ describe('Events API', () => { method: 'GET', url: '/api/events/devcard-conf-2025', }); - + console.log(JSON.stringify(res.json(), null, 2)); expect(res.statusCode).toBe(200); const body = res.json(); expect(body.slug).toBe('devcard-conf-2025'); @@ -275,7 +286,7 @@ describe('Events API', () => { method: 'GET', url: '/api/events/ghost-event', }); - + expect(res.statusCode).toBe(404); expect(res.json()).toMatchObject({ error: 'Event not found' }); }); @@ -285,6 +296,7 @@ describe('Events API', () => { mockJwtVerify.mockRejectedValue(new Error('Should not be called')); prismaMock.event.findUnique.mockResolvedValue({ ...MOCK_EVENT, + organizer: { username: 'johndoe', displayName: 'John Doe' }, _count: { attendees: 0 }, }); @@ -495,6 +507,7 @@ describe('Events API', () => { prismaMock.event.findUnique.mockResolvedValue({ ...MOCK_EVENT, attendees: attendeeRows, + _count: { attendees: 2 }, }); const res = await app.inject({ @@ -523,6 +536,7 @@ describe('Events API', () => { prismaMock.event.findUnique.mockResolvedValue({ ...MOCK_EVENT, attendees: [makeAttendeeRow(MOCK_OTHER_USER_PROFILE)], + _count: { attendees: 1 }, }); const res = await app.inject({ @@ -545,6 +559,7 @@ describe('Events API', () => { prismaMock.event.findUnique.mockResolvedValue({ ...MOCK_EVENT, attendees: [], + _count: { attendees: 0 }, }); const res = await app.inject({ @@ -561,6 +576,7 @@ describe('Events API', () => { prismaMock.event.findUnique.mockResolvedValue({ ...MOCK_EVENT, attendees: [], + _count: { attendees: 0 }, }); const res = await app.inject({ @@ -577,6 +593,7 @@ describe('Events API', () => { prismaMock.event.findUnique.mockResolvedValue({ ...MOCK_EVENT, attendees: [], + _count: { attendees: 0 }, }); const res = await app.inject({ @@ -594,6 +611,7 @@ describe('Events API', () => { prismaMock.event.findUnique.mockResolvedValue({ ...MOCK_EVENT, attendees: [makeAttendeeRow(MOCK_USER_PROFILE)], + _count: { attendees: 1 }, }); const res = await app.inject({ diff --git a/apps/backend/src/routes/cards.ts b/apps/backend/src/routes/cards.ts index 8b8d6ff2..f7030ecc 100644 --- a/apps/backend/src/routes/cards.ts +++ b/apps/backend/src/routes/cards.ts @@ -62,15 +62,9 @@ function hasErrorCode( } export async function cardRoutes(app: FastifyInstance): Promise { - app.addHook('preHandler', async (request, reply) => { - const server = request.server; - if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } - if (typeof app.authenticate === 'function') { await app.authenticate(request, reply); return } - try { await request.jwtVerify() } catch (_e) { reply.status(401).send({ error: 'Unauthorized' }) } - }); - + // ─── List Cards ─── - app.get('/', async (request: FastifyRequest, reply: FastifyReply): Promise => { + app.get('/', {preHandler: [(req, reply) => app.authenticate(req, reply)] },async (request: FastifyRequest, reply: FastifyReply): Promise => { const userId = request.user.id; try { return await cardService.listCards(app, userId) @@ -80,7 +74,7 @@ export async function cardRoutes(app: FastifyInstance): Promise { }); // ─── Creates Card ─── - app.post('/', async (request: FastifyRequest<{ Body: CreateCardBody }>, reply: FastifyReply): Promise => { + app.post<{ Body: CreateCardBody }>('/', { preHandler: [(req, reply) => app.authenticate(req, reply)]}, async (request, reply): Promise => { const userId = request.user.id; const parsed = createCardSchema.safeParse(request.body); @@ -99,7 +93,7 @@ export async function cardRoutes(app: FastifyInstance): Promise { // ─── Update Card ─── - app.put('/:id', async (request: FastifyRequest<{ Params: CardParams; Body: UpdateCardBody }>, reply: FastifyReply): Promise => { + app.put<{ Params: CardParams; Body: UpdateCardBody }>('/:id', {preHandler: [(req, reply) => app.authenticate(req, reply)] }, async (request, reply): Promise => { const userId = request.user.id; const { id } = request.params; @@ -117,7 +111,7 @@ export async function cardRoutes(app: FastifyInstance): Promise { // ─── Delete Card ─── - app.delete('/:id', async (request: FastifyRequest<{ Params: CardParams }>, reply: FastifyReply): Promise => { + app.delete<{ Params: CardParams }>('/:id', { preHandler: [(req, reply) => app.authenticate(req, reply)]}, async (request, reply): Promise => { const userId = request.user.id; const { id } = request.params; @@ -139,7 +133,7 @@ export async function cardRoutes(app: FastifyInstance): Promise { }); // ─── Set Default Card ─── - app.put('/:id/default', async (request: FastifyRequest<{ Params: CardParams }>, reply: FastifyReply): Promise => { + app.put<{ Params: CardParams }>('/:id/default', {preHandler: [(req, reply) => app.authenticate(req, reply)]}, async (request, reply): Promise => { const userId = request.user.id; const { id } = request.params; diff --git a/apps/backend/src/routes/event.ts b/apps/backend/src/routes/event.ts index 8d7bc566..38c02d72 100644 --- a/apps/backend/src/routes/event.ts +++ b/apps/backend/src/routes/event.ts @@ -1,7 +1,7 @@ -import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; -import { createEventSchema, joinEventSchema} from '../validations/event.validation.js'; - import {generateUniqueSlug} from '../utils/slug.js' +import { createEventSchema} from '../validations/event.validation.js'; + +import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; type EventDetails = { @@ -58,21 +58,8 @@ type EventWithAttendees = { } export async function eventRoutes(app:FastifyInstance) { - app.post('/', { preHandler: [async (request, reply) => { - const server = request.server as any; - if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } - if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } - try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } - }] }, async (request: FastifyRequest<{ - Body: { - name: string, - description?: string, - startDate: string, - location: string, - endDate: string, - isPublic?: boolean - }}>, reply: FastifyReply) => { - const userId = (request.user as any).id; + app.post<{Body: { name: string; description?: string; startDate: string; location: string; endDate: string; isPublic?: boolean; }}>('/', { preHandler: [(req, reply) => app.authenticate(req, reply)] }, async (request, reply) => { + const userId = request.user.id; const parsed = createEventSchema.safeParse(request.body); if(!parsed.success){ return reply.status(400).send({error: 'Bad request'}) @@ -80,8 +67,8 @@ export async function eventRoutes(app:FastifyInstance) { const {name, description, startDate, endDate, isPublic ,location} = parsed.data - let finalSlug = await generateUniqueSlug(name, async(slug) => { - const existing = await app.prisma.event.findUnique({where: {slug : slug}}) + const finalSlug = await generateUniqueSlug(name, async(slug) => { + const existing = await app.prisma.event.findUnique({where: {slug}}) return !!existing }) @@ -95,7 +82,7 @@ export async function eventRoutes(app:FastifyInstance) { name, description, slug: finalSlug, - location: location, + location, startDate: startDateObj, endDate: endDateObj, isPublic: isPublic ?? true, @@ -104,7 +91,7 @@ export async function eventRoutes(app:FastifyInstance) { }) return reply.status(201).send(newEvent); - } catch (error) { + } catch (_error) { app.log.error('Failed to create event'); return reply.status(500).send({error: 'Failed to create event'}) } @@ -153,8 +140,8 @@ export async function eventRoutes(app:FastifyInstance) { return response; }) - app.post('/:slug/join', { preHandler: [async (request, reply) => { const server = request.server as any; if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } }] }, async(request: FastifyRequest<{Params: {slug: string}}>, reply: FastifyReply) => { - const userId = (request.user as any).id; + app.post<{ Params: { slug: string } }>('/:slug/join', {preHandler: [(req, reply) => app.authenticate(req, reply)]}, async(request, reply) => { + const userId = request.user.id; const paramsSlug = request.params.slug; const event = await app.prisma.event.findUnique({ @@ -171,7 +158,7 @@ export async function eventRoutes(app:FastifyInstance) { await app.prisma.eventAttendee.create({ data: { eventId: event.id, - userId: userId, + userId, joinedAt: new Date() } }) @@ -186,9 +173,9 @@ export async function eventRoutes(app:FastifyInstance) { } }) + app.delete<{Params: {slug: string}}>('/:slug/leave',{preHandler: [(req, reply) => app.authenticate(req, reply)]}, async(request, reply) => { - app.delete('/:slug/leave', { preHandler: [async (request, reply) => { const server = request.server as any; if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } }] }, async(request: FastifyRequest<{Params: {slug: string}}>, reply: FastifyReply) => { - const userId = (request.user as any).id; + const userId = request.user.id; const paramsSlug = request.params.slug; const event = await app.prisma.event.findUnique({ @@ -205,7 +192,7 @@ export async function eventRoutes(app:FastifyInstance) { await app.prisma.eventAttendee.delete({ where: { userId_eventId: { - userId: userId, + userId, eventId: event.id } } diff --git a/apps/backend/src/routes/nfc.ts b/apps/backend/src/routes/nfc.ts index 5cf13f0c..647f8549 100644 --- a/apps/backend/src/routes/nfc.ts +++ b/apps/backend/src/routes/nfc.ts @@ -1,6 +1,7 @@ -import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import { z } from 'zod'; +import type { FastifyInstance} from 'fastify'; + type NfcPayloadResponse = { type: 'URI'; payload: string; @@ -11,32 +12,15 @@ const nfcQuerySchema = z.object({ }); export async function nfcRoutes(app: FastifyInstance) { - app.addHook('preHandler', async (request, reply) => { - const server = request.server as any; - if (typeof server?.authenticate === 'function') { - await server.authenticate(request, reply); - return; - } - if (typeof (app as any).authenticate === 'function') { - await (app as any).authenticate(request, reply); - return; - } - try { - await request.jwtVerify(); - } catch (e) { - reply.status(401).send({ error: 'Unauthorized' }); - } - }); + // GET /api/nfc/payload — returns NDEF URI payload for user's default DevCard URL // GET /api/nfc/payload?card= — returns payload for a specific card - app.get( - '/payload', - async ( - request: FastifyRequest<{ Querystring: { card?: string } }>, - reply: FastifyReply - ) => { - const userId = (request.user as any).id; + app.get<{ Querystring: { card?: string } }>( + '/payload', + { preHandler: [(req, reply) => app.authenticate(req, reply)] }, + async (request, reply) => { + const userId = request.user.id; // Validate query params with Zod const parseResult = nfcQuerySchema.safeParse(request.query);