diff --git a/packages/s3/src/index.ts b/packages/s3/src/index.ts index 202d4d9d1..5b36aa686 100644 --- a/packages/s3/src/index.ts +++ b/packages/s3/src/index.ts @@ -14,7 +14,10 @@ export { default as FileService } from "./model/files/service"; export { default as S3Client } from "./utils/s3Client"; export type { FilePayload, Multipart, S3Config } from "./types"; export type { File, FileCreateInput, FileUpdateInput } from "./types/file"; -export type { FileUpload as GraphQLFileUpload } from "graphql-upload-minimal"; +export type { + FileUpload as GraphQLFileUpload, + Upload as GraphQLUpload, +} from "graphql-upload-minimal"; export { default } from "./plugin"; export { default as ajvFilePlugin } from "./plugins/ajvFile"; diff --git a/packages/s3/src/plugins/multipartParser.ts b/packages/s3/src/plugins/multipartParser.ts index 21eb7fb6d..9ad790cea 100644 --- a/packages/s3/src/plugins/multipartParser.ts +++ b/packages/s3/src/plugins/multipartParser.ts @@ -20,13 +20,13 @@ const plugin = async (fastify: FastifyInstance) => { req.routeOptions.url?.startsWith(req.config.graphql.path as string) ) { req.graphqlFileUploadMultipart = true; - - // eslint-disable-next-line unicorn/no-null - done(null); } else { processMultipartFormData(req, _payload, done); } } + + // eslint-disable-next-line unicorn/no-null + done(null); }); }; diff --git a/packages/user/README.md b/packages/user/README.md index 3dbad3d37..ffcb14d56 100644 --- a/packages/user/README.md +++ b/packages/user/README.md @@ -32,7 +32,7 @@ Register the user plugin with your Fastify instance: ```typescript import configPlugin from "@prefabs.tech/fastify-config"; import mailerPlugin from "@prefabs.tech/fastify-mailer"; -import s3Plugin from "@prefabs.tech/fastify-s3"; +import s3Plugin, { multipartParserPlugin } from "@prefabs.tech/fastify-s3"; import slonikPlugin, { migrationPlugin } from "@prefabs.tech/fastify-slonik"; import userPlugin from "@prefabs.tech/fastify-user"; import Fastify from "fastify"; @@ -56,6 +56,9 @@ const start = async () => { // Register mailer plugin await fastify.register(mailerPlugin, config.mailer); + + // Register multipart content-type parser plugin + await api.register(multipartParserPlugin); // Register mailer plugin await fastify.register(s3Plugin); diff --git a/packages/user/src/model/users/graphql/resolver.ts b/packages/user/src/model/users/graphql/resolver.ts index 9db5903d8..2459ea6c2 100644 --- a/packages/user/src/model/users/graphql/resolver.ts +++ b/packages/user/src/model/users/graphql/resolver.ts @@ -1,4 +1,4 @@ -import { GraphQLFileUpload, Multipart } from "@prefabs.tech/fastify-s3"; +import { GraphQLUpload, Multipart } from "@prefabs.tech/fastify-s3"; import { mercurius } from "mercurius"; import EmailVerification, { EmailVerificationClaim, @@ -310,14 +310,13 @@ const Mutation = { uploadPhoto: async ( parent: unknown, arguments_: { - photo: { - file: GraphQLFileUpload; - }; + photo: GraphQLUpload; }, context: MercuriusContext, ) => { const { app, config, database, dbSchema, reply, user } = context; - const { photo } = arguments_; + const photo = await arguments_.photo; + const { file: photoFile } = photo; const service = getUserService(config, database, dbSchema); @@ -325,7 +324,7 @@ const Mutation = { return new mercurius.ErrorWithProps("unauthorized", {}, 401); } - if (!photo) { + if (!photoFile) { throw new CustomApiError({ message: "Missing photo file in the request body", name: "ERROR_FILE_MISSING", @@ -334,10 +333,10 @@ const Mutation = { } try { - const fileData = photo.file.createReadStream(); + const fileData = photoFile.createReadStream(); const fileToUpload: Multipart = { - ...photo.file, + ...photoFile, data: fileData, limit: false, }; diff --git a/packages/user/src/model/users/graphql/schema.ts b/packages/user/src/model/users/graphql/schema.ts index 810134a95..a225cb20f 100644 --- a/packages/user/src/model/users/graphql/schema.ts +++ b/packages/user/src/model/users/graphql/schema.ts @@ -1,6 +1,8 @@ import { gql } from "@prefabs.tech/fastify-graphql"; const user = gql` + scalar Upload + type User { id: String! deletedAt: Float @@ -89,217 +91,4 @@ const user = gql` } `; -const errorSchema = { - type: "object", - properties: { - code: { type: "string" }, - error: { type: "object" }, - message: { type: "string" }, - statusCode: { type: "number" }, - status: { type: "string" }, - }, -}; - -const userSchema = { - type: "object", - properties: { - id: { type: "string" }, - email: { type: "string", format: "email" }, - firstName: { type: "string" }, - lastName: { type: "string" }, - role: { type: "string" }, - status: { type: "string" }, - createdAt: { type: "number" }, - updatedAt: { type: "number" }, - }, - required: ["id", "email", "role", "status", "createdAt", "updatedAt"], -}; - -export const createUserSchema = { - description: "Create a new user", - operationId: "createUser", - body: { - type: "object", - required: ["email", "password", "role"], - properties: { - email: { type: "string", format: "email" }, - password: { type: "string", format: "password" }, - firstName: { type: "string" }, - lastName: { type: "string" }, - role: { type: "string" }, - }, - }, - response: { - 201: userSchema, - 400: { - description: "Bad Request", - ...errorSchema, - }, - 401: { - description: "Unauthorized", - ...errorSchema, - }, - 403: { - description: "Forbidden", - ...errorSchema, - }, - 500: { - ...errorSchema, - }, - }, - tags: ["users"], -}; - -export const deleteUserSchema = { - description: "Delete a user by ID", - operationId: "deleteUser", - params: { - type: "object", - required: ["id"], - properties: { - id: { type: "string" }, - }, - }, - response: { - 200: { - type: "object", - properties: { - status: { type: "string" }, - }, - }, - 401: { - description: "Unauthorized", - ...errorSchema, - }, - 403: { - description: "Forbidden", - ...errorSchema, - }, - 404: { - description: "User not found", - ...errorSchema, - }, - 500: { - ...errorSchema, - }, - }, - tags: ["users"], -}; - -export const getUserSchema = { - description: "Get a user by ID", - operationId: "getUser", - params: { - type: "object", - required: ["id"], - properties: { - id: { type: "string" }, - }, - }, - response: { - 200: userSchema, - 401: { - description: "Unauthorized", - ...errorSchema, - }, - 403: { - description: "Forbidden", - ...errorSchema, - }, - 404: { - description: "User not found", - ...errorSchema, - }, - 500: { - ...errorSchema, - }, - }, - tags: ["users"], -}; - -export const getUsersSchema = { - description: - "Get a paginated list of users with optional filtering and sorting", - operationId: "getUsers", - querystring: { - type: "object", - properties: { - limit: { type: "number" }, - offset: { type: "number" }, - filters: { type: "string" }, - sort: { type: "string" }, - }, - }, - response: { - 200: { - type: "object", - required: ["totalCount", "filteredCount", "data"], - properties: { - totalCount: { type: "integer" }, - filteredCount: { type: "integer" }, - data: { - type: "array", - items: userSchema, - }, - }, - }, - 401: { - description: "Unauthorized", - ...errorSchema, - }, - 403: { - description: "Forbidden", - ...errorSchema, - }, - 500: { - ...errorSchema, - }, - }, - tags: ["users"], -}; - -export const updateUserSchema = { - description: "Update a user's information", - operationId: "updateUser", - params: { - type: "object", - required: ["id"], - properties: { - id: { type: "string" }, - }, - }, - body: { - type: "object", - properties: { - firstName: { type: "string" }, - lastName: { type: "string" }, - role: { type: "string" }, - status: { type: "string" }, - }, - }, - response: { - 200: userSchema, - 400: { - description: "Bad Request", - ...errorSchema, - }, - 401: { - description: "Unauthorized", - ...errorSchema, - }, - 403: { - description: "Forbidden", - ...errorSchema, - }, - 404: { - description: "User not found", - ...errorSchema, - }, - 500: { - ...errorSchema, - }, - }, - tags: ["users"], -}; - export default user;