diff --git a/packages/medusa-js/src/resources/admin/uploads.ts b/packages/medusa-js/src/resources/admin/uploads.ts index 2b24373259..3d83cc2f79 100644 --- a/packages/medusa-js/src/resources/admin/uploads.ts +++ b/packages/medusa-js/src/resources/admin/uploads.ts @@ -1,14 +1,21 @@ -import { AdminUploadRes, IAdminPostUploadsFile } from "@medusajs/medusa" +import { + AdminDeleteUploadsReq, + IAdminPostUploadsFileReq, + AdminDeleteUploadsRes, + AdminPostUploadsDownloadUrlReq, + AdminUploadsDownloadUrlRes, + AdminUploadsRes, +} from "@medusajs/medusa" +import FormData from "form-data" import { ResponsePromise } from "../../typings" import BaseResource from "../base" -import FormData from "form-data" class AdminUploadsResource extends BaseResource { private headers = { "Content-Type": "multipart/form-data", } - create(file: IAdminPostUploadsFile): ResponsePromise { + create(file: IAdminPostUploadsFileReq): ResponsePromise { const path = `/admin/uploads` const payload = new FormData() @@ -16,6 +23,24 @@ class AdminUploadsResource extends BaseResource { return this.client.request("POST", path, payload, {}, this.headers) } + + delete( + payload: AdminDeleteUploadsReq, + customHeaders: Record = {} + ): ResponsePromise { + const path = `/admin/uploads` + + return this.client.request("DELETE", path, payload, {}, customHeaders) + } + + getPresignedDownloadUrl( + payload: AdminPostUploadsDownloadUrlReq, + customHeaders: Record = {} + ): ResponsePromise { + const path = `/admin/uploads/download-url` + + return this.client.request("POST", path, payload, {}, customHeaders) + } } export default AdminUploadsResource diff --git a/packages/medusa-react/mocks/data/fixtures.json b/packages/medusa-react/mocks/data/fixtures.json index 9966b00119..99fc9da78d 100644 --- a/packages/medusa-react/mocks/data/fixtures.json +++ b/packages/medusa-react/mocks/data/fixtures.json @@ -1258,6 +1258,9 @@ "updated_at": "2021-03-16T21:24:00.389Z", "created_at": "2021-03-16T21:24:00.389Z", "deleted_at": null + }, + "upload": { + "url": "test-url" } } } diff --git a/packages/medusa-react/mocks/handlers/admin.ts b/packages/medusa-react/mocks/handlers/admin.ts index 33d52aa3f6..49ec7d072a 100644 --- a/packages/medusa-react/mocks/handlers/admin.ts +++ b/packages/medusa-react/mocks/handlers/admin.ts @@ -1,4 +1,5 @@ import { rest } from "msw" +import { body } from "msw/lib/types/context" import { fixtures } from "../data" export const adminHandlers = [ @@ -1651,4 +1652,24 @@ export const adminHandlers = [ rest.delete("/admin/auth", (req, res, ctx) => { return res(ctx.status(200)) }), + + rest.delete("/admin/uploads", (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + id: (req.body as any).file_key, + object: "file", + deleted: true, + }) + ) + }), + + rest.post("/admin/uploads/download-url", (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + download_url: fixtures.get("upload").url, + }) + ) + }), ] diff --git a/packages/medusa-react/src/hooks/admin/index.ts b/packages/medusa-react/src/hooks/admin/index.ts index 556e6cdab6..89873d1ebe 100644 --- a/packages/medusa-react/src/hooks/admin/index.ts +++ b/packages/medusa-react/src/hooks/admin/index.ts @@ -25,3 +25,4 @@ export * from "./swaps" export * from "./tax-rates" export * from "./users" export * from "./variants" +export * from "./uploads" diff --git a/packages/medusa-react/src/hooks/admin/uploads/index.ts b/packages/medusa-react/src/hooks/admin/uploads/index.ts new file mode 100644 index 0000000000..bd086bcaef --- /dev/null +++ b/packages/medusa-react/src/hooks/admin/uploads/index.ts @@ -0,0 +1 @@ +export * from "./mutations" diff --git a/packages/medusa-react/src/hooks/admin/uploads/mutations.ts b/packages/medusa-react/src/hooks/admin/uploads/mutations.ts new file mode 100644 index 0000000000..32cfd4fa04 --- /dev/null +++ b/packages/medusa-react/src/hooks/admin/uploads/mutations.ts @@ -0,0 +1,60 @@ +import { + AdminDeleteUploadsReq, + IAdminPostUploadsFileReq, + AdminDeleteUploadsRes, + AdminPostUploadsDownloadUrlReq, + AdminUploadsDownloadUrlRes, + AdminUploadsRes, +} from "@medusajs/medusa" +import { Response } from "@medusajs/medusa-js" +import { useMutation, UseMutationOptions, useQueryClient } from "react-query" +import { useMedusa } from "../../../contexts" +import { buildOptions } from "../../utils/buildOptions" + +export const useAdminUploadFile = ( + options?: UseMutationOptions< + Response, + Error, + IAdminPostUploadsFileReq + > +) => { + const { client } = useMedusa() + const queryClient = useQueryClient() + + return useMutation((payload: IAdminPostUploadsFileReq) => { + return client.admin.uploads.create(payload) + }, buildOptions(queryClient, [], options)) +} + +export const useAdminCreatePresignedDownloadUrl = ( + options?: UseMutationOptions< + Response, + Error, + AdminPostUploadsDownloadUrlReq + > +) => { + const { client } = useMedusa() + const queryClient = useQueryClient() + + return useMutation( + (payload: AdminPostUploadsDownloadUrlReq) => + client.admin.uploads.getPresignedDownloadUrl(payload), + buildOptions(queryClient, [], options) + ) +} + +export const useAdminDeleteFile = ( + options?: UseMutationOptions< + Response, + Error, + AdminDeleteUploadsReq + > +) => { + const { client } = useMedusa() + const queryClient = useQueryClient() + + return useMutation( + (payload: AdminDeleteUploadsReq) => client.admin.uploads.delete(payload), + buildOptions(queryClient, [], options) + ) +} diff --git a/packages/medusa-react/test/hooks/admin/uploads/mutations.test.ts b/packages/medusa-react/test/hooks/admin/uploads/mutations.test.ts new file mode 100644 index 0000000000..2393217b02 --- /dev/null +++ b/packages/medusa-react/test/hooks/admin/uploads/mutations.test.ts @@ -0,0 +1,46 @@ +import { renderHook } from "@testing-library/react-hooks" +import { fixtures } from "../../../../mocks/data" +import { + useAdminDeleteFile, + useAdminCreatePresignedDownloadUrl, +} from "../../../../src" +import { createWrapper } from "../../../utils" + +describe("useAdminDeleteFile hook", () => { + test("Removes file with key and returns deleteresult", async () => { + const file_key = "test" + + const { result, waitFor } = renderHook(() => useAdminDeleteFile(), { + wrapper: createWrapper(), + }) + + result.current.mutate({ file_key }) + + await waitFor(() => result.current.isSuccess) + + expect(result.current.data.response.status).toEqual(200) + expect(result.current.data).toEqual( + expect.objectContaining({ id: file_key, object: "file", deleted: true }) + ) + }) +}) + +describe("useAdminCreatePresignedDownloadUrl hook", () => { + test("", async () => { + const file_key = "test" + + const { result, waitFor } = renderHook( + () => useAdminCreatePresignedDownloadUrl(), + { + wrapper: createWrapper(), + } + ) + + result.current.mutate({ file_key }) + + await waitFor(() => result.current.isSuccess) + + expect(result.current.data.response.status).toEqual(200) + expect(result.current.data.download_url).toEqual(fixtures.get("upload").url) + }) +}) diff --git a/packages/medusa/src/api/routes/admin/uploads/create-upload.ts b/packages/medusa/src/api/routes/admin/uploads/create-upload.ts index 86320c33db..5c9f7e1ba1 100644 --- a/packages/medusa/src/api/routes/admin/uploads/create-upload.ts +++ b/packages/medusa/src/api/routes/admin/uploads/create-upload.ts @@ -37,7 +37,7 @@ export default async (req, res) => { } } -export class IAdminPostUploadsFile { +export class IAdminPostUploadsFileReq { originalName: string path: string } diff --git a/packages/medusa/src/api/routes/admin/uploads/delete-upload.ts b/packages/medusa/src/api/routes/admin/uploads/delete-upload.ts index 5a4d2e5440..25be5f5d1d 100644 --- a/packages/medusa/src/api/routes/admin/uploads/delete-upload.ts +++ b/packages/medusa/src/api/routes/admin/uploads/delete-upload.ts @@ -1,9 +1,8 @@ import { IsString } from "class-validator" -import { validator } from "../../../../utils/validator" /** * [delete] /uploads - * operationId: "AdminDeleteUpload" + * operationId: "AdminDeleteUploads" * summary: "Removes an uploaded file" * description: "Removes an uploaded file using the installed fileservice" * x-authenticated: true @@ -14,9 +13,7 @@ import { validator } from "../../../../utils/validator" * description: OK */ export default async (req, res) => { - const validated = await validator(AdminDeleteUploadReq, req.body, { - forbidUnknownValues: false, - }) + const validated = req.validatedBody as AdminDeleteUploadsReq const fileService = req.scope.resolve("fileService") @@ -27,7 +24,7 @@ export default async (req, res) => { .send({ id: validated.file_key, object: "file", deleted: true }) } -class AdminDeleteUploadReq { +export class AdminDeleteUploadsReq { @IsString() file_key: string } diff --git a/packages/medusa/src/api/routes/admin/uploads/get-download-url.ts b/packages/medusa/src/api/routes/admin/uploads/get-download-url.ts index 6dfe7ccb93..44aa2b2e52 100644 --- a/packages/medusa/src/api/routes/admin/uploads/get-download-url.ts +++ b/packages/medusa/src/api/routes/admin/uploads/get-download-url.ts @@ -1,12 +1,11 @@ import { IsString } from "class-validator" import { AbstractFileService } from "../../../../interfaces" -import { validator } from "../../../../utils/validator" /** - * [get] /uploads - * operationId: "GetUploadsFileDownloadUrl" - * summary: "Gets a presigned download url for a file" - * description: "Gets a presigned download url for a file" + * [post] /uploads/download-url + * operationId: "PostUploadsDownloadUrl" + * summary: "Creates a presigned download url for a file" + * description: "Creates a presigned download url for a file" * x-authenticated: true * requestBody: * content: @@ -26,18 +25,16 @@ import { validator } from "../../../../utils/validator" * description: OK */ export default async (req, res) => { - const validated = await validator(AdminGetUploadsFileDownloadUrlReq, req.body) - const fileService: AbstractFileService = req.scope.resolve("fileService") const url = await fileService.getPresignedDownloadUrl({ - fileKey: validated.file_key, + fileKey: (req.validatedBody as AdminPostUploadsDownloadUrlReq).file_key, }) res.status(200).send({ download_url: url }) } -class AdminGetUploadsFileDownloadUrlReq { +export class AdminPostUploadsDownloadUrlReq { @IsString() file_key: string } diff --git a/packages/medusa/src/api/routes/admin/uploads/index.ts b/packages/medusa/src/api/routes/admin/uploads/index.ts index 60ee1b4ef8..c5d0daa042 100644 --- a/packages/medusa/src/api/routes/admin/uploads/index.ts +++ b/packages/medusa/src/api/routes/admin/uploads/index.ts @@ -2,7 +2,9 @@ import { Router } from "express" import multer from "multer" import { DeleteResponse } from "../../../../types/common" -import middlewares from "../../../middlewares" +import middlewares, { transformBody } from "../../../middlewares" +import { AdminDeleteUploadsReq } from "./delete-upload" +import { AdminPostUploadsDownloadUrlReq } from "./get-download-url" const route = Router() const upload = multer({ dest: "uploads/" }) @@ -16,21 +18,30 @@ export default (app) => { middlewares.wrap(require("./create-upload").default) ) - route.delete("/", middlewares.wrap(require("./delete-upload").default)) + route.delete( + "/", + transformBody(AdminDeleteUploadsReq), + middlewares.wrap(require("./delete-upload").default) + ) - route.get( + route.post( "/download-url", + transformBody(AdminPostUploadsDownloadUrlReq), middlewares.wrap(require("./get-download-url").default) ) return app } -export type AdminUploadRes = { - uploads: unknown[] +export type AdminUploadsRes = { + uploads: { url: string }[] } -export type AdminDeleteUploadRes = DeleteResponse +export type AdminDeleteUploadsRes = DeleteResponse + +export type AdminUploadsDownloadUrlRes = { + download_url: string +} export * from "./create-upload" export * from "./delete-upload"