From b00469f6a3777f3d34556e54ae7489841f0acfc9 Mon Sep 17 00:00:00 2001 From: Ramesh Date: Thu, 31 Jul 2025 13:25:49 +0545 Subject: [PATCH 1/5] chore: cleanup user graphql schema --- .../user/src/model/users/graphql/schema.ts | 213 ------------------ 1 file changed, 213 deletions(-) diff --git a/packages/user/src/model/users/graphql/schema.ts b/packages/user/src/model/users/graphql/schema.ts index 810134a95..140d4b9ea 100644 --- a/packages/user/src/model/users/graphql/schema.ts +++ b/packages/user/src/model/users/graphql/schema.ts @@ -89,217 +89,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; From 757f3abfc06e7dd1c966da417a7fe1cf22a5e11d Mon Sep 17 00:00:00 2001 From: Ramesh Date: Thu, 31 Jul 2025 16:36:58 +0545 Subject: [PATCH 2/5] refactor: update export statement for GraphQL file upload types --- packages/s3/src/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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"; From 4291e1a6fb3ff016ed0d5f3958c940a088df3c13 Mon Sep 17 00:00:00 2001 From: Ramesh Date: Thu, 31 Jul 2025 16:40:29 +0545 Subject: [PATCH 3/5] fix: ensure done callback is called correctly in multipart parser --- packages/s3/src/plugins/multipartParser.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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); }); }; From a0422ef469ca2b1785c9af097875f9e4ef634f54 Mon Sep 17 00:00:00 2001 From: Ramesh Date: Thu, 31 Jul 2025 16:42:19 +0545 Subject: [PATCH 4/5] fix: update photo upload handling to use GraphQLUpload type --- packages/user/src/model/users/graphql/resolver.ts | 15 +++++++-------- packages/user/src/model/users/graphql/schema.ts | 2 ++ 2 files changed, 9 insertions(+), 8 deletions(-) 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 140d4b9ea..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 From 8f1b74f1002d326a6ea211bd09b3b1a9352182b4 Mon Sep 17 00:00:00 2001 From: Ramesh Date: Thu, 31 Jul 2025 16:46:08 +0545 Subject: [PATCH 5/5] docs: update readme file --- packages/user/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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);