diff --git a/apps/backend/src/__tests__/team.test.ts b/apps/backend/src/__tests__/team.test.ts index 350298a1..0e97bb11 100644 --- a/apps/backend/src/__tests__/team.test.ts +++ b/apps/backend/src/__tests__/team.test.ts @@ -1,6 +1,7 @@ +import { type PrismaClient, TeamRole } from '@prisma/client'; +import Fastify, { type FastifyInstance } from 'fastify'; import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import Fastify, { FastifyInstance } from 'fastify'; -import { PrismaClient, TeamRole } from '@prisma/client'; + import { teamRoutes } from '../routes/team'; // ─── Shared mock data ───────────────────────────────────────────────────────── @@ -92,7 +93,7 @@ const prismaMock = { // ─── App factory ────────────────────────────────────────────────────────────── -let mockJwtVerify = vi.fn(); +const mockJwtVerify = vi.fn(); async function buildApp(): Promise { const app = Fastify({ logger: false }); @@ -102,7 +103,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' }); + } + }); await app.register(teamRoutes); await app.ready(); return app; @@ -118,7 +126,7 @@ async function createTeam( app: FastifyInstance, body: Record, authenticated = true, -) { +): Promise>> { return app.inject({ method: 'POST', url: '/', diff --git a/apps/backend/src/plugins/prisma.ts b/apps/backend/src/plugins/prisma.ts index f6ebede8..abe40961 100644 --- a/apps/backend/src/plugins/prisma.ts +++ b/apps/backend/src/plugins/prisma.ts @@ -1,14 +1,11 @@ -import fp from 'fastify-plugin'; import { PrismaClient } from '@prisma/client'; -import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; +import fp from 'fastify-plugin'; + +import type { FastifyInstance } from 'fastify'; declare module 'fastify' { interface FastifyInstance { prisma: PrismaClient; - authenticate( - request: FastifyRequest, - reply: FastifyReply - ): Promise; } } diff --git a/apps/backend/src/routes/team.ts b/apps/backend/src/routes/team.ts index af177e52..48c239d6 100644 --- a/apps/backend/src/routes/team.ts +++ b/apps/backend/src/routes/team.ts @@ -5,7 +5,7 @@ import {generateUniqueSlug} from '../utils/slug' import { createTeamScehma,inviteMembers,updateTeam } from '../validations/team.validation'; import type {PlatformLink, PublicProfile} from '@devcard/shared' -import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; +import type { FastifyInstance } from 'fastify'; type TeamMember = PublicProfile & { teamRole: TeamRole @@ -24,16 +24,11 @@ type TeamProfile = { members: TeamMember[]; } -export async function teamRoutes(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 { const payload = await request.jwtVerify(); if (payload) (request as any).user = payload; } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } - }] }, async(request:FastifyRequest<{ - Body: {name: string, description? : string, avatarUrl?: string } - }>, reply: FastifyReply) => { - const userId = (request.user as any).id; +export async function teamRoutes(app: FastifyInstance): Promise { + app.post<{ + Body: {name: string, description? : string, avatarUrl?: string } + }>('/',{ preHandler: [app.authenticate] }, async (request, reply): Promise => { + const userId = request.user.id; const parsed = createTeamScehma.safeParse(request.body); if(!parsed.success){ return reply.status(400).send({error: 'Bad request'}) @@ -48,7 +43,7 @@ export async function teamRoutes(app:FastifyInstance){ try { const team = await app.prisma.$transaction(async (tx) => { - const team = await tx.team.create({ + const createdTeam = await tx.team.create({ data: { name, slug: finalSlug, @@ -60,13 +55,13 @@ export async function teamRoutes(app:FastifyInstance){ await tx.teamMember.create({ data: { - teamId : team.id, + teamId : createdTeam.id, userId, role: TeamRole.OWNER, joinedAt: new Date(), } }) - return team + return createdTeam }) return reply.status(201).send(team) @@ -91,7 +86,7 @@ export async function teamRoutes(app:FastifyInstance){ } }) - app.get('/:slug', async(request: FastifyRequest<{Params: {slug: string}}>, reply: FastifyReply) => { + app.get<{Params: {slug: string}}>('/:slug', async (request, reply): Promise => { const paramsSlug = request.params.slug; try { @@ -149,7 +144,7 @@ export async function teamRoutes(app:FastifyInstance){ members } - return response; + return reply.status(200).send(response); } catch (error) { app.log.error(error); return reply.status(500).send('Database query failed') @@ -157,14 +152,9 @@ export async function teamRoutes(app:FastifyInstance){ }) - app.post('/:slug/members', { 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 { const payload = await request.jwtVerify(); if (payload) (request as any).user = payload; } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } - }] }, async(request: FastifyRequest<{Params: {slug:string}, Body:{username:string}}>, reply: FastifyReply) => { + app.post<{Params: {slug:string}, Body:{username:string}}>('/:slug/members', { preHandler: [app.authenticate] }, async (request, reply): Promise => { const paramsSlug = request.params.slug; - const userId = (request.user as any).id; + const userId = request.user.id; const parsed = inviteMembers.safeParse(request.body); if(!parsed.success){ return reply.status(400).send({error: 'Bad request'}) @@ -224,10 +214,10 @@ export async function teamRoutes(app:FastifyInstance){ } }) - app.delete('/:slug/members/:userId', { 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, userId: string}}>, reply: FastifyReply) => { + app.delete<{Params: {slug: string, userId: string}}>('/:slug/members/:userId',{ preHandler: [app.authenticate] }, async (request, reply): Promise => { const paramsSlug = request.params.slug const paramsUserId = request.params.userId - const userID = (request.user as any).id; + const userId = request.user.id; const teamDetails = await app.prisma.team.findUnique( {where: {slug: paramsSlug}, include: { @@ -251,8 +241,8 @@ export async function teamRoutes(app:FastifyInstance){ }); } - const isOwner = teamDetails.ownerId === userID; - const isSelfRemove = paramsUserId === userID; + const isOwner = teamDetails.ownerId === userId; + const isSelfRemove = paramsUserId === userId; if (!isOwner && !isSelfRemove) { return reply.status(403).send({ @@ -286,8 +276,10 @@ export async function teamRoutes(app:FastifyInstance){ } }) - app.patch('/:slug',{ 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},Body: {description?:string, name?:string, avatarUrl?:string}}>, reply: FastifyReply) => { - const userId = (request.user as any).id; + app.patch<{Params: {slug: string},Body: {description?:string, name?:string, avatarUrl?:string}}>('/:slug',{ preHandler: [app.authenticate + + ] }, async (request, reply): Promise => { + const userId = request.user.id; const paramsSlug = request.params.slug; const parsed = updateTeam.safeParse(request.body); if(!parsed.success){ @@ -328,8 +320,8 @@ export async function teamRoutes(app:FastifyInstance){ }) - app.delete('/:slug',{ 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.delete<{Params:{slug: string}}>('/:slug',{ preHandler: [app.authenticate] }, async (request, reply): Promise => { + const userId = request.user.id; const paramsSlug = request.params.slug; @@ -364,7 +356,7 @@ export async function teamRoutes(app:FastifyInstance){ } }) - app.get('/:slug/qr',async(request:FastifyRequest<{Params:{slug:string}}>, reply: FastifyReply) => { + app.get<{Params:{slug:string}}>('/:slug/qr', async (request, reply): Promise => { const paramsSlug = request.params.slug; try { const teamDetails = await app.prisma.team.findUnique({ @@ -386,4 +378,4 @@ export async function teamRoutes(app:FastifyInstance){ } }) -} \ No newline at end of file +} diff --git a/apps/backend/src/types/fastify.d.ts b/apps/backend/src/types/fastify.d.ts index faeddd2a..10515b5e 100644 --- a/apps/backend/src/types/fastify.d.ts +++ b/apps/backend/src/types/fastify.d.ts @@ -19,6 +19,6 @@ declare module 'fastify' { } interface FastifyInstance { - authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise; + authenticate: (this: void, request: FastifyRequest, reply: FastifyReply) => Promise; } }