From cd4fbc851b0fc766c93552971cb916e4ccd1ef55 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 6 May 2025 12:55:51 -0400 Subject: improve compatibility with multipart/form-data mastodon API requests --- .../src/server/api/mastodon/endpoints/account.ts | 53 +++++++++------------- .../src/server/api/mastodon/endpoints/apps.ts | 5 +- .../src/server/api/mastodon/endpoints/filter.ts | 7 ++- .../server/api/mastodon/endpoints/notifications.ts | 7 ++- 4 files changed, 29 insertions(+), 43 deletions(-) (limited to 'packages/backend/src/server/api/mastodon/endpoints') diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 8bc3c14c15..b4ce56408e 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -11,7 +11,6 @@ import { DI } from '@/di-symbols.js'; import type { AccessTokensRepository, UserProfilesRepository } from '@/models/_.js'; import { attachMinMaxPagination } from '@/server/api/mastodon/pagination.js'; import { MastodonConverters, convertRelationship, convertFeaturedTag, convertList } from '../MastodonConverters.js'; -import type multer from 'fastify-multer'; import type { FastifyInstance } from 'fastify'; interface ApiAccountMastodonRoute { @@ -34,7 +33,7 @@ export class ApiAccountMastodon { private readonly driveService: DriveService, ) {} - public register(fastify: FastifyInstance, upload: ReturnType): void { + public register(fastify: FastifyInstance): void { fastify.get('/v1/accounts/verify_credentials', async (_request, reply) => { const client = this.clientService.getClient(_request); const data = await client.verifyAccountCredentials(); @@ -70,60 +69,50 @@ export class ApiAccountMastodon { value: string, }[], }, - }>('/v1/accounts/update_credentials', { preHandler: upload.any() }, async (_request, reply) => { + }>('/v1/accounts/update_credentials', async (_request, reply) => { const accessTokens = _request.headers.authorization; const client = this.clientService.getClient(_request); // Check if there is a Header or Avatar being uploaded, if there is proceed to upload it to the drive of the user and then set it. - if (_request.files.length > 0 && accessTokens) { + if (_request.savedRequestFiles?.length && accessTokens) { const tokeninfo = await this.accessTokensRepository.findOneBy({ token: accessTokens.replace('Bearer ', '') }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const avatar = (_request.files as any).find((obj: any) => { + const avatar = _request.savedRequestFiles.find(obj => { return obj.fieldname === 'avatar'; }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const header = (_request.files as any).find((obj: any) => { + const header = _request.savedRequestFiles.find(obj => { return obj.fieldname === 'header'; }); if (tokeninfo && avatar) { const upload = await this.driveService.addFile({ user: { id: tokeninfo.userId, host: null }, - path: avatar.path, - name: avatar.originalname !== null && avatar.originalname !== 'file' ? avatar.originalname : undefined, + path: avatar.filepath, + name: avatar.filename && avatar.filename !== 'file' ? avatar.filename : undefined, sensitive: false, }); if (upload.type.startsWith('image/')) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (_request.body as any).avatar = upload.id; + _request.body.avatar = upload.id; } } else if (tokeninfo && header) { const upload = await this.driveService.addFile({ user: { id: tokeninfo.userId, host: null }, - path: header.path, - name: header.originalname !== null && header.originalname !== 'file' ? header.originalname : undefined, + path: header.filepath, + name: header.filename && header.filename !== 'file' ? header.filename : undefined, sensitive: false, }); if (upload.type.startsWith('image/')) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (_request.body as any).header = upload.id; + _request.body.header = upload.id; } } } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((_request.body as any).fields_attributes) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const fields = (_request.body as any).fields_attributes.map((field: any) => { + if (_request.body.fields_attributes) { + for (const field of _request.body.fields_attributes) { if (!(field.name.trim() === '' && field.value.trim() === '')) { if (field.name.trim() === '') return reply.code(400).send('Field name can not be empty'); if (field.value.trim() === '') return reply.code(400).send('Field value can not be empty'); } - return { - ...field, - }; - }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (_request.body as any).fields_attributes = fields.filter((field: any) => field.name.trim().length > 0 && field.value.length > 0); + } + _request.body.fields_attributes = _request.body.fields_attributes.filter(field => field.name.trim().length > 0 && field.value.length > 0); } const options = { @@ -234,7 +223,7 @@ export class ApiAccountMastodon { reply.send(response); }); - fastify.post('/v1/accounts/:id/follow', { preHandler: upload.single('none') }, async (_request, reply) => { + fastify.post('/v1/accounts/:id/follow', async (_request, reply) => { if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' }); const client = this.clientService.getClient(_request); @@ -245,7 +234,7 @@ export class ApiAccountMastodon { reply.send(acct); }); - fastify.post('/v1/accounts/:id/unfollow', { preHandler: upload.single('none') }, async (_request, reply) => { + fastify.post('/v1/accounts/:id/unfollow', async (_request, reply) => { if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' }); const client = this.clientService.getClient(_request); @@ -256,7 +245,7 @@ export class ApiAccountMastodon { reply.send(acct); }); - fastify.post('/v1/accounts/:id/block', { preHandler: upload.single('none') }, async (_request, reply) => { + fastify.post('/v1/accounts/:id/block', async (_request, reply) => { if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' }); const client = this.clientService.getClient(_request); @@ -266,7 +255,7 @@ export class ApiAccountMastodon { reply.send(response); }); - fastify.post('/v1/accounts/:id/unblock', { preHandler: upload.single('none') }, async (_request, reply) => { + fastify.post('/v1/accounts/:id/unblock', async (_request, reply) => { if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' }); const client = this.clientService.getClient(_request); @@ -276,7 +265,7 @@ export class ApiAccountMastodon { return reply.send(response); }); - fastify.post('/v1/accounts/:id/mute', { preHandler: upload.single('none') }, async (_request, reply) => { + fastify.post('/v1/accounts/:id/mute', async (_request, reply) => { if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' }); const client = this.clientService.getClient(_request); @@ -289,7 +278,7 @@ export class ApiAccountMastodon { reply.send(response); }); - fastify.post('/v1/accounts/:id/unmute', { preHandler: upload.single('none') }, async (_request, reply) => { + fastify.post('/v1/accounts/:id/unmute', async (_request, reply) => { if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' }); const client = this.clientService.getClient(_request); diff --git a/packages/backend/src/server/api/mastodon/endpoints/apps.ts b/packages/backend/src/server/api/mastodon/endpoints/apps.ts index dbef3b7d35..ec08600e53 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/apps.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/apps.ts @@ -6,7 +6,6 @@ import { Injectable } from '@nestjs/common'; import { MastodonClientService } from '@/server/api/mastodon/MastodonClientService.js'; import type { FastifyInstance } from 'fastify'; -import type multer from 'fastify-multer'; const readScope = [ 'read:account', @@ -62,8 +61,8 @@ export class ApiAppsMastodon { private readonly clientService: MastodonClientService, ) {} - public register(fastify: FastifyInstance, upload: ReturnType): void { - fastify.post('/v1/apps', { preHandler: upload.single('none') }, async (_request, reply) => { + public register(fastify: FastifyInstance): void { + fastify.post('/v1/apps', async (_request, reply) => { const body = _request.body ?? _request.query; if (!body.scopes) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "scopes"' }); if (!body.redirect_uris) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "redirect_uris"' }); diff --git a/packages/backend/src/server/api/mastodon/endpoints/filter.ts b/packages/backend/src/server/api/mastodon/endpoints/filter.ts index deac1e9aad..242f068b99 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/filter.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/filter.ts @@ -8,7 +8,6 @@ import { toBoolean } from '@/server/api/mastodon/argsUtils.js'; import { MastodonClientService } from '@/server/api/mastodon/MastodonClientService.js'; import { convertFilter } from '../MastodonConverters.js'; import type { FastifyInstance } from 'fastify'; -import type multer from 'fastify-multer'; interface ApiFilterMastodonRoute { Params: { @@ -29,7 +28,7 @@ export class ApiFilterMastodon { private readonly clientService: MastodonClientService, ) {} - public register(fastify: FastifyInstance, upload: ReturnType): void { + public register(fastify: FastifyInstance): void { fastify.get('/v1/filters', async (_request, reply) => { const client = this.clientService.getClient(_request); @@ -49,7 +48,7 @@ export class ApiFilterMastodon { reply.send(response); }); - fastify.post('/v1/filters', { preHandler: upload.single('none') }, async (_request, reply) => { + fastify.post('/v1/filters', async (_request, reply) => { if (!_request.body.phrase) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "phrase"' }); if (!_request.body.context) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "context"' }); @@ -68,7 +67,7 @@ export class ApiFilterMastodon { reply.send(response); }); - fastify.post('/v1/filters/:id', { preHandler: upload.single('none') }, async (_request, reply) => { + fastify.post('/v1/filters/:id', async (_request, reply) => { if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' }); if (!_request.body.phrase) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "phrase"' }); if (!_request.body.context) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "context"' }); diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts index ee6c990fd1..75512c2efc 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts @@ -10,7 +10,6 @@ import { MastodonConverters } from '@/server/api/mastodon/MastodonConverters.js' import { attachMinMaxPagination } from '@/server/api/mastodon/pagination.js'; import { MastodonClientService } from '../MastodonClientService.js'; import type { FastifyInstance } from 'fastify'; -import type multer from 'fastify-multer'; interface ApiNotifyMastodonRoute { Params: { @@ -26,7 +25,7 @@ export class ApiNotificationsMastodon { private readonly clientService: MastodonClientService, ) {} - public register(fastify: FastifyInstance, upload: ReturnType): void { + public register(fastify: FastifyInstance): void { fastify.get('/v1/notifications', async (request, reply) => { const { client, me } = await this.clientService.getAuthClient(request); const data = await client.getNotifications(parseTimelineArgs(request.query)); @@ -66,7 +65,7 @@ export class ApiNotificationsMastodon { reply.send(response); }); - fastify.post('/v1/notification/:id/dismiss', { preHandler: upload.single('none') }, async (_request, reply) => { + fastify.post('/v1/notification/:id/dismiss', async (_request, reply) => { if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' }); const client = this.clientService.getClient(_request); @@ -75,7 +74,7 @@ export class ApiNotificationsMastodon { reply.send(data.data); }); - fastify.post('/v1/notifications/clear', { preHandler: upload.single('none') }, async (_request, reply) => { + fastify.post('/v1/notifications/clear', async (_request, reply) => { const client = this.clientService.getClient(_request); const data = await client.dismissNotifications(); -- cgit v1.2.3-freya