diff --git a/packages/s3/src/plugins/multipartParser.ts b/packages/s3/src/plugins/multipartParser.ts index 1746dd02a..21eb7fb6d 100644 --- a/packages/s3/src/plugins/multipartParser.ts +++ b/packages/s3/src/plugins/multipartParser.ts @@ -11,8 +11,10 @@ declare module "fastify" { } const plugin = async (fastify: FastifyInstance) => { - if (!fastify.hasContentTypeParser("multipart")) { - fastify.addContentTypeParser("multipart", (req, _payload, done) => { + fastify.addContentTypeParser("*", (req, _payload, done) => { + const contentType = req.headers["content-type"] || ""; + + if (contentType.includes("multipart")) { if ( req.config.graphql?.enabled && req.routeOptions.url?.startsWith(req.config.graphql.path as string) @@ -24,8 +26,8 @@ const plugin = async (fastify: FastifyInstance) => { } else { processMultipartFormData(req, _payload, done); } - }); - } + } + }); }; export default fastifyPlugin(plugin); diff --git a/packages/user/src/model/users/graphql/resolver.ts b/packages/user/src/model/users/graphql/resolver.ts index 51243a13e..9db5903d8 100644 --- a/packages/user/src/model/users/graphql/resolver.ts +++ b/packages/user/src/model/users/graphql/resolver.ts @@ -1,3 +1,4 @@ +import { GraphQLFileUpload, Multipart } from "@prefabs.tech/fastify-s3"; import { mercurius } from "mercurius"; import EmailVerification, { EmailVerificationClaim, @@ -306,6 +307,91 @@ const Mutation = { return mercuriusError; } }, + uploadPhoto: async ( + parent: unknown, + arguments_: { + photo: { + file: GraphQLFileUpload; + }; + }, + context: MercuriusContext, + ) => { + const { app, config, database, dbSchema, reply, user } = context; + const { photo } = arguments_; + + const service = getUserService(config, database, dbSchema); + + if (!user) { + return new mercurius.ErrorWithProps("unauthorized", {}, 401); + } + + if (!photo) { + throw new CustomApiError({ + message: "Missing photo file in the request body", + name: "ERROR_FILE_MISSING", + statusCode: 422, + }); + } + + try { + const fileData = photo.file.createReadStream(); + + const fileToUpload: Multipart = { + ...photo.file, + data: fileData, + limit: false, + }; + + const file = await service.uploadPhoto(fileToUpload, user.id, user.id); + + const updatedUser = await service.update(user.id, { + ...(file && { + photoId: file.id as number, + }), + }); + + if (user.photoId && user.photoId !== updatedUser.photoId) { + await service.fileService.delete(user.photoId); + } + + const request = reply.request; + + request.user = updatedUser; + + if (request.config.user.features?.profileValidation?.enabled) { + await request.session?.fetchAndSetClaim( + new ProfileValidationClaim(), + createUserContext(undefined, request), + ); + } + + if (request.config.user.features?.signUp?.emailVerification) { + await request.session?.fetchAndSetClaim( + EmailVerificationClaim, + createUserContext(undefined, request), + ); + } + + return updatedUser; + } catch (error) { + if (error instanceof CustomApiError) { + const mercuriusError = new mercurius.ErrorWithProps(error.name); + + mercuriusError.statusCode = error.statusCode; + + return mercuriusError; + } + + app.log.error(error); + + const mercuriusError = new mercurius.ErrorWithProps( + "Oops, Something went wrong", + ); + mercuriusError.statusCode = 500; + + return mercuriusError; + } + }, removePhoto: async ( parent: unknown, arguments_: undefined, diff --git a/packages/user/src/model/users/graphql/schema.ts b/packages/user/src/model/users/graphql/schema.ts index 53ce4d5ff..810134a95 100644 --- a/packages/user/src/model/users/graphql/schema.ts +++ b/packages/user/src/model/users/graphql/schema.ts @@ -75,6 +75,8 @@ const user = gql` @auth(profileValidation: false, emailVerification: false) changeEmail(email: String!): ChangeEmailResponse @auth(profileValidation: false, emailVerification: false) + uploadPhoto(photo: Upload!): User + @auth(profileValidation: false, emailVerification: false) removePhoto: User @auth(profileValidation: false, emailVerification: false) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 167776cab..bfa97b25c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4251,6 +4251,11 @@ packages: mercurius-auth@6.0.0: resolution: {integrity: sha512-pwxoF2xrYaItu9GFpNMFzNULmC7JCC3XCRzYtUOG8TgmrCtQ5N0SSKnA92FGkt/Q/5/3FfFJIiF4GSW6iRbolQ==} + mercurius-upload@8.0.0: + resolution: {integrity: sha512-lKYtqo6enNHRhFa6MrGE2OLf5/G0CvaXLCFp/Wczb6vS14LxtWor1fPwayNbiHV3RCS9DsiMnqCWdWaySZxKWA==} + peerDependencies: + graphql: ^16.3.0 + mercurius@16.1.0: resolution: {integrity: sha512-foMnqtFn3Wf/3zh0IJEz30Dq0alZIlIMCoQpxpS3/GkDB12h/LfmcoZ6ASCZ8xBJIvStEIKYxOyjfDeEcYhjLQ==} engines: {node: ^20.9.0 || >=22.0.0} @@ -10485,6 +10490,12 @@ snapshots: fastify-plugin: 5.0.1 graphql: 16.11.0 + mercurius-upload@8.0.0(graphql@16.11.0): + dependencies: + fastify-plugin: 5.0.1 + graphql: 16.11.0 + graphql-upload-minimal: 1.6.1(graphql@16.11.0) + mercurius@16.1.0(graphql@16.9.0): dependencies: '@fastify/error': 4.0.0