-
Notifications
You must be signed in to change notification settings - Fork 120
fix(backend): remove any usages from profiles.ts to improve type safety #589
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,10 @@ | ||
| import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; | ||
| import { getProfileUrl } from '@devcard/shared'; | ||
| import { updateProfileSchema, createLinkSchema, reorderLinksSchema } from '../utils/validators.js'; | ||
| import { getErrorMessage } from '../utils/error.util.js'; | ||
| import * as profileService from '../services/profileService' | ||
| import { Prisma } from '@prisma/client'; | ||
|
|
||
| // ── Response types ──────────────────────────────────────────────────────────── | ||
| // Declared explicitly so the API contract is visible without tracing through | ||
| // Prisma's generic return types. Follows the convention in public.ts. | ||
| import * as profileService from '../services/profileService'; | ||
| import { updateProfileSchema, createLinkSchema, reorderLinksSchema } from '../utils/validators.js'; | ||
|
|
||
| import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; | ||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| type ProfileUpdateResponse = { | ||
| id: string; | ||
| email: string; | ||
|
|
@@ -21,38 +18,40 @@ type ProfileUpdateResponse = { | |
| accentColor: string; | ||
| }; | ||
|
|
||
| export async function profileRoutes(app: FastifyInstance) { | ||
| export async function profileRoutes(app: FastifyInstance): Promise<void> { | ||
| // All profile routes require auth | ||
| app.addHook('preHandler', async (request, reply) => { | ||
| const server = request.server as any; | ||
| const server = request.server; | ||
| 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); | ||
| if (typeof app.authenticate === 'function') { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that the casts are gone, the typing makes the redundancy here visible: |
||
| await app.authenticate(request, reply); | ||
| return; | ||
| } | ||
|
Comment on lines
+24
to
32
|
||
| try { | ||
| await request.jwtVerify(); | ||
| } catch (e) { | ||
| } catch (_e) { | ||
| reply.status(401).send({ error: 'Unauthorized' }); | ||
| } | ||
| }); | ||
|
|
||
| // ─── Get Own Profile ─── | ||
|
|
||
| app.get('/me', async (request: FastifyRequest, reply: FastifyReply) => { | ||
| const userId = (request.user as any).id; | ||
| const userId = request.user.id; | ||
|
|
||
| const user = await profileService.getOwnProfile(app, userId) | ||
| if (!user) return reply.status(404).send({ error: 'User not found' }) | ||
| if (!user) { | ||
| return reply.status(404).send({ error: 'User not found' }); | ||
| } | ||
| return user | ||
| }); | ||
|
|
||
| // ─── Update Profile ─── | ||
|
|
||
| app.put('/me', async (request: FastifyRequest, reply: FastifyReply) => { | ||
| const userId = (request.user as any).id; | ||
| const userId = request.user.id; | ||
| const parsed = updateProfileSchema.safeParse(request.body); | ||
|
|
||
| if (!parsed.success) { | ||
|
|
@@ -79,8 +78,10 @@ export async function profileRoutes(app: FastifyInstance) { | |
| try { | ||
| const response = await profileService.updateProfile(app, userId, parsed.data) | ||
| return response | ||
| } catch (err: any) { | ||
| if (err?.code === 'P2002') return reply.status(409).send({ error: 'Username already taken' }) | ||
| } catch (err: unknown) { | ||
| if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === 'P2002') { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good change. Since |
||
| return reply.status(409).send({ error: 'Username already taken' }); | ||
| } | ||
| app.log.error({ err }, 'DB error in PUT /profiles/me') | ||
| return reply.status(500).send({ error: 'Internal server error' }) | ||
| } | ||
|
|
@@ -89,7 +90,7 @@ export async function profileRoutes(app: FastifyInstance) { | |
| // ─── Add Platform Link ─── | ||
|
|
||
| app.post('/me/links', async (request: FastifyRequest, reply: FastifyReply) => { | ||
| const userId = (request.user as any).id; | ||
| const userId = request.user.id; | ||
| const parsed = createLinkSchema.safeParse(request.body); | ||
|
|
||
| if (!parsed.success) { | ||
|
|
@@ -99,7 +100,7 @@ export async function profileRoutes(app: FastifyInstance) { | |
| try { | ||
| const link = await profileService.createPlatformLink(app, userId, parsed.data) | ||
| return reply.status(201).send(link) | ||
| } catch (err: any) { | ||
| } catch (err: unknown) { | ||
| app.log.error({ err }, 'Failed to create platform link') | ||
| return reply.status(500).send({ error: 'Internal server error' }) | ||
| } | ||
|
|
@@ -108,16 +109,20 @@ export async function profileRoutes(app: FastifyInstance) { | |
| // ─── Update Platform Link ─── | ||
|
|
||
| app.put('/me/links/:id', async (request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) => { | ||
| const userId = (request.user as any).id; | ||
| const userId = request.user.id; | ||
| const { id } = request.params; | ||
|
|
||
| const parsedReq = createLinkSchema.safeParse(request.body) | ||
| if (!parsedReq.success) return reply.status(400).send({ error: 'Validation failed', details: parsedReq.error.flatten() }) | ||
| if (!parsedReq.success) { | ||
| return reply.status(400).send({ error: 'Validation failed', details: parsedReq.error.flatten() }); | ||
| } | ||
| try { | ||
| const updated = await profileService.updatePlatformLink(app, userId, id, parsedReq.data) | ||
| if (!updated) return reply.status(404).send({ error: 'Link not found' }) | ||
| if (!updated) { | ||
| return reply.status(404).send({ error: 'Link not found' }); | ||
| } | ||
| return updated | ||
| } catch (err: any) { | ||
| } catch (err: unknown) { | ||
| app.log.error({ err }, 'Failed to update platform link') | ||
| return reply.status(500).send({ error: 'Internal server error' }) | ||
| } | ||
|
|
@@ -126,14 +131,16 @@ export async function profileRoutes(app: FastifyInstance) { | |
| // ─── Delete Platform Link ─── | ||
|
|
||
| app.delete('/me/links/:id', async (request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) => { | ||
| const userId = (request.user as any).id; | ||
| const userId = request.user.id; | ||
| const { id } = request.params; | ||
|
|
||
| try { | ||
| const deleted = await profileService.deletePlatformLink(app, userId, id) | ||
| if (!deleted) return reply.status(404).send({ error: 'Link not found' }) | ||
| if (!deleted) { | ||
| return reply.status(404).send({ error: 'Link not found' }); | ||
| } | ||
| return reply.status(204).send() | ||
| } catch (err: any) { | ||
| } catch (err: unknown) { | ||
| app.log.error({ err }, 'Failed to delete platform link') | ||
| return reply.status(500).send({ error: 'Internal server error' }) | ||
| } | ||
|
|
@@ -142,13 +149,15 @@ export async function profileRoutes(app: FastifyInstance) { | |
| // ─── Reorder Links ─── | ||
|
|
||
| app.put('/me/links/reorder', async (request: FastifyRequest, reply: FastifyReply) => { | ||
| const userId = (request.user as any).id; | ||
| const userId = request.user.id; | ||
| const parsedReq = reorderLinksSchema.safeParse(request.body) | ||
| if (!parsedReq.success) return reply.status(400).send({ error: 'Validation failed', details: parsedReq.error.flatten() }) | ||
| if (!parsedReq.success) { | ||
| return reply.status(400).send({ error: 'Validation failed', details: parsedReq.error.flatten() }); | ||
| } | ||
| try { | ||
| const resp = await profileService.reorderLinks(app, userId, parsedReq.data.links) | ||
| return resp | ||
| } catch (err: any) { | ||
| } catch (err: unknown) { | ||
| app.log.error({ err }, 'Failed to reorder links') | ||
| return reply.status(500).send({ error: 'Internal server error' }) | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ProfileUpdateResponseis now suppressed with an eslint-disable for being unused rather than removed. The PR description says no behavior changes and this type isn't referenced anywhere — prefer deleting the dead type outright over silencing the linter. If it's intentionally kept as living API documentation, a one-line comment saying so would be clearer than the bare disable.