From 5b0603342a138bd6d7d5def5a52ced2a49d68edd Mon Sep 17 00:00:00 2001 From: Yachika Sharma Date: Mon, 15 Jun 2026 21:59:43 +0530 Subject: [PATCH 1/5] fix: improve type safety in team.ts, remove any usages (closes #554) --- apps/backend/src/routes/team.ts | 56 ++++++++++++++------------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/apps/backend/src/routes/team.ts b/apps/backend/src/routes/team.ts index af177e52..80f370f7 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.bind(app)] }, 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.bind(app)] }, 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.bind(app)] }, 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,8 @@ 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.bind(app)] }, 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 +318,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.bind(app)] }, async (request, reply): Promise => { + const userId = request.user.id; const paramsSlug = request.params.slug; @@ -364,7 +354,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 +376,4 @@ export async function teamRoutes(app:FastifyInstance){ } }) -} \ No newline at end of file +} From 0ef001e0413665a2d174cf1a682d58ffc7414d0e Mon Sep 17 00:00:00 2001 From: Yachika Sharma Date: Wed, 17 Jun 2026 14:50:15 +0530 Subject: [PATCH 2/5] fix: resolve broken test suite from team.ts type-safety refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add authenticate decorator inside buildApp() before app.register(teamRoutes), since Fastify executes plugin registration during app.ready() — decorator must exist before routes are registered, not after buildApp() returns - Remove redundant .bind(app) calls in team.ts; authenticate decorator doesn't use 'this', consistent with auth.ts pattern - Cast payload via 'as typeof request.user' to satisfy TS in test mock --- apps/backend/src/__tests__/team.test.ts | 9 ++++++++- apps/backend/src/routes/team.ts | 12 +++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/backend/src/__tests__/team.test.ts b/apps/backend/src/__tests__/team.test.ts index 350298a1..894ae4cb 100644 --- a/apps/backend/src/__tests__/team.test.ts +++ b/apps/backend/src/__tests__/team.test.ts @@ -102,7 +102,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; diff --git a/apps/backend/src/routes/team.ts b/apps/backend/src/routes/team.ts index 80f370f7..48c239d6 100644 --- a/apps/backend/src/routes/team.ts +++ b/apps/backend/src/routes/team.ts @@ -27,7 +27,7 @@ type TeamProfile = { export async function teamRoutes(app: FastifyInstance): Promise { app.post<{ Body: {name: string, description? : string, avatarUrl?: string } - }>('/',{ preHandler: [app.authenticate.bind(app)] }, async (request, reply): Promise => { + }>('/',{ preHandler: [app.authenticate] }, async (request, reply): Promise => { const userId = request.user.id; const parsed = createTeamScehma.safeParse(request.body); if(!parsed.success){ @@ -152,7 +152,7 @@ export async function teamRoutes(app: FastifyInstance): Promise { }) - app.post<{Params: {slug:string}, Body:{username:string}}>('/:slug/members', { preHandler: [app.authenticate.bind(app)] }, async (request, reply): Promise => { + 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.id; const parsed = inviteMembers.safeParse(request.body); @@ -214,7 +214,7 @@ export async function teamRoutes(app: FastifyInstance): Promise { } }) - app.delete<{Params: {slug: string, userId: string}}>('/:slug/members/:userId',{ preHandler: [app.authenticate.bind(app)] }, async (request, reply): Promise => { + 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.id; @@ -276,7 +276,9 @@ export async function teamRoutes(app: FastifyInstance): Promise { } }) - app.patch<{Params: {slug: string},Body: {description?:string, name?:string, avatarUrl?:string}}>('/:slug',{ preHandler: [app.authenticate.bind(app)] }, async (request, reply): Promise => { + 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); @@ -318,7 +320,7 @@ export async function teamRoutes(app: FastifyInstance): Promise { }) - app.delete<{Params:{slug: string}}>('/:slug',{ preHandler: [app.authenticate.bind(app)] }, async (request, reply): Promise => { + app.delete<{Params:{slug: string}}>('/:slug',{ preHandler: [app.authenticate] }, async (request, reply): Promise => { const userId = request.user.id; const paramsSlug = request.params.slug; From 7d4b23dbe9bf91972954bbfd6819563a2408bcfa Mon Sep 17 00:00:00 2001 From: Yachika Sharma Date: Wed, 17 Jun 2026 15:43:57 +0530 Subject: [PATCH 3/5] fix: resolve lint/type errors blocking team.ts type-safety PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add authenticate decorator inside buildApp() before app.register(teamRoutes); Fastify executes plugin registration during app.ready(), so the decorator must exist before routes are registered - Remove redundant .bind(app) in team.ts; authenticate decorator doesn't use 'this' - Fix eslint errors in team.test.ts: type-only imports, import order, const over let, missing curly braces (mostly via --fix) - Add explicit return type to createTeam test helper - Add 'this: void' to authenticate's type signature in fastify.d.ts so @typescript-eslint/unbound-method allows referencing it without binding - Remove duplicate authenticate declaration in prisma.ts (method-shorthand syntax with implicit bound this) that was silently conflicting with the fastify.d.ts declaration — tsc merged them without error since the call signatures were structurally compatible, but eslint's unbound-method rule was still resolving to the unbound declaration --- apps/backend/src/__tests__/team.test.ts | 11 ++++++----- apps/backend/src/plugins/prisma.ts | 4 ---- apps/backend/src/types/fastify.d.ts | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/apps/backend/src/__tests__/team.test.ts b/apps/backend/src/__tests__/team.test.ts index 894ae4cb..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 }); @@ -105,7 +106,7 @@ async function buildApp(): Promise { app.decorate('authenticate', async function (request, reply) { try { const payload = await request.jwtVerify(); - if (payload) request.user = payload as typeof request.user; + if (payload) {request.user = payload as typeof request.user;} } catch { return reply.status(401).send({ error: 'Unauthorized' }); } @@ -125,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..a4cf6ac2 100644 --- a/apps/backend/src/plugins/prisma.ts +++ b/apps/backend/src/plugins/prisma.ts @@ -5,10 +5,6 @@ import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; declare module 'fastify' { interface FastifyInstance { prisma: PrismaClient; - authenticate( - request: FastifyRequest, - reply: FastifyReply - ): Promise; } } 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; } } From 1bad9bba1b471be1ffa5582be39b22795cf83dfb Mon Sep 17 00:00:00 2001 From: Yachika Sharma Date: Wed, 17 Jun 2026 15:54:12 +0530 Subject: [PATCH 4/5] chore: remove unused import --- apps/backend/src/plugins/prisma.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/backend/src/plugins/prisma.ts b/apps/backend/src/plugins/prisma.ts index a4cf6ac2..abe40961 100644 --- a/apps/backend/src/plugins/prisma.ts +++ b/apps/backend/src/plugins/prisma.ts @@ -1,6 +1,7 @@ -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 { From bc3a4f2377769a198271327c526cdc6435f8d6bb Mon Sep 17 00:00:00 2001 From: Yachika Sharma Date: Mon, 22 Jun 2026 16:19:10 +0530 Subject: [PATCH 5/5] Update team.ts Signed-off-by: Yachika Sharma --- apps/backend/src/routes/team.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/backend/src/routes/team.ts b/apps/backend/src/routes/team.ts index 7a372228..6ab7dfe3 100644 --- a/apps/backend/src/routes/team.ts +++ b/apps/backend/src/routes/team.ts @@ -4,7 +4,7 @@ import QRCode from 'qrcode' import {generateUniqueSlug} from '../utils/slug.js' import { createTeamScehma,inviteMembers,updateTeam } from '../validations/team.validation.js'; -import type {PlatformLink, PublicProfile} from '@devcard/shared' +import type {PlatformLink, PublicProfile} from '@devcard/shared/src/types.js' import type { FastifyInstance } from 'fastify'; type TeamMember = PublicProfile & {