feat(medusa): Product category, type and tags
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductCollectionServiceMock } from "../../../../../services/__mocks__/product-collection"
|
||||
|
||||
describe("POST /admin/collections", () => {
|
||||
describe("successful creation", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request("POST", "/admin/collections", {
|
||||
payload: {
|
||||
title: "Suits",
|
||||
handle: "suits",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("returns created product collection", () => {
|
||||
expect(subject.body.collection.id).toEqual(IdMap.getId("col"))
|
||||
})
|
||||
|
||||
it("calls production collection service create", () => {
|
||||
expect(ProductCollectionServiceMock.create).toHaveBeenCalledTimes(1)
|
||||
expect(ProductCollectionServiceMock.create).toHaveBeenCalledWith({
|
||||
title: "Suits",
|
||||
handle: "suits",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("invalid data returns error details", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request("POST", "/admin/collections", {
|
||||
payload: {
|
||||
handle: "no-title-collection",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 400", () => {
|
||||
expect(subject.status).toEqual(400)
|
||||
})
|
||||
|
||||
it("returns error details", () => {
|
||||
expect(subject.body.name).toEqual("invalid_data")
|
||||
expect(subject.body.message[0].message).toEqual(`"title" is required`)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,42 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductCollectionServiceMock } from "../../../../../services/__mocks__/product-collection"
|
||||
|
||||
describe("DELETE /admin/collections/:id", () => {
|
||||
describe("successful removes collection", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"DELETE",
|
||||
`/admin/collections/${IdMap.getId("collection")}`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls product collection service delete", () => {
|
||||
expect(ProductCollectionServiceMock.delete).toHaveBeenCalledTimes(1)
|
||||
expect(ProductCollectionServiceMock.delete).toHaveBeenCalledWith(
|
||||
IdMap.getId("collection")
|
||||
)
|
||||
})
|
||||
|
||||
it("returns delete result", () => {
|
||||
expect(subject.body).toEqual({
|
||||
id: IdMap.getId("collection"),
|
||||
object: "product-collection",
|
||||
deleted: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,37 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductCollectionServiceMock } from "../../../../../services/__mocks__/product-collection"
|
||||
|
||||
describe("GET /admin/categories/:id", () => {
|
||||
describe("get collection by id successfully", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"GET",
|
||||
`/admin/collections/${IdMap.getId("col")}`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls retrieve from product collection service", () => {
|
||||
expect(ProductCollectionServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(ProductCollectionServiceMock.retrieve).toHaveBeenCalledWith(
|
||||
IdMap.getId("col")
|
||||
)
|
||||
})
|
||||
|
||||
it("returns variant decorated", () => {
|
||||
expect(subject.body.collection.id).toEqual(IdMap.getId("col"))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,28 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductCollectionServiceMock } from "../../../../../services/__mocks__/product-collection"
|
||||
|
||||
describe("GET /admin/collections", () => {
|
||||
describe("successful retrieval", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request("GET", `/admin/collections`, {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls product collection service list", () => {
|
||||
expect(ProductCollectionServiceMock.list).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,44 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductCollectionServiceMock } from "../../../../../services/__mocks__/product-collection"
|
||||
|
||||
describe("POST /admin/collections/:id", () => {
|
||||
describe("successful update", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/collections/${IdMap.getId("col")}`,
|
||||
{
|
||||
payload: {
|
||||
title: "Suits and vests",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("returns updated product collection", () => {
|
||||
expect(subject.body.collection.id).toEqual(IdMap.getId("col"))
|
||||
})
|
||||
|
||||
it("product collection service update", () => {
|
||||
expect(ProductCollectionServiceMock.update).toHaveBeenCalledTimes(1)
|
||||
expect(ProductCollectionServiceMock.update).toHaveBeenCalledWith(
|
||||
IdMap.getId("col"),
|
||||
{
|
||||
title: "Suits and vests",
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
title: Validator.string().required(),
|
||||
handle: Validator.string()
|
||||
.optional()
|
||||
.allow(""),
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const productCollectionService = req.scope.resolve(
|
||||
"productCollectionService"
|
||||
)
|
||||
|
||||
const created = await productCollectionService.create(value)
|
||||
const collection = await productCollectionService.retrieve(created.id)
|
||||
|
||||
res.status(200).json({ collection })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
try {
|
||||
const productCollectionService = req.scope.resolve(
|
||||
"productCollectionService"
|
||||
)
|
||||
await productCollectionService.delete(id)
|
||||
|
||||
res.json({
|
||||
id,
|
||||
object: "product-collection",
|
||||
deleted: true,
|
||||
})
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
try {
|
||||
const productCollectionService = req.scope.resolve(
|
||||
"productCollectionService"
|
||||
)
|
||||
|
||||
const collection = await productCollectionService.retrieve(id)
|
||||
res.status(200).json({ collection })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Router } from "express"
|
||||
import middlewares from "../../../middlewares"
|
||||
|
||||
const route = Router()
|
||||
|
||||
export default app => {
|
||||
app.use("/collections", route)
|
||||
|
||||
route.post("/", middlewares.wrap(require("./create-collection").default))
|
||||
route.post("/:id", middlewares.wrap(require("./update-collection").default))
|
||||
|
||||
route.delete("/:id", middlewares.wrap(require("./delete-collection").default))
|
||||
|
||||
route.get("/:id", middlewares.wrap(require("./get-collection").default))
|
||||
route.get("/", middlewares.wrap(require("./list-collections").default))
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
export const defaultFields = ["id", "title", "handle"]
|
||||
export const defaultRelations = ["products"]
|
||||
@@ -0,0 +1,30 @@
|
||||
import { defaultFields, defaultRelations } from "."
|
||||
|
||||
export default async (req, res) => {
|
||||
try {
|
||||
const selector = {}
|
||||
|
||||
const limit = parseInt(req.query.limit) || 10
|
||||
const offset = parseInt(req.query.offset) || 0
|
||||
|
||||
const productCollectionService = req.scope.resolve(
|
||||
"productCollectionService"
|
||||
)
|
||||
|
||||
const listConfig = {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
skip: offset,
|
||||
take: limit,
|
||||
}
|
||||
|
||||
const collections = await productCollectionService.list(
|
||||
selector,
|
||||
listConfig
|
||||
)
|
||||
|
||||
res.status(200).json({ collections })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
title: Validator.string().optional(),
|
||||
handle: Validator.string().optional(),
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const productCollectionService = req.scope.resolve(
|
||||
"productCollectionService"
|
||||
)
|
||||
|
||||
const updated = await productCollectionService.update(id, value)
|
||||
const collection = await productCollectionService.retrieve(updated.id)
|
||||
|
||||
res.status(200).json({ collection })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import appRoutes from "./apps"
|
||||
import swapRoutes from "./swaps"
|
||||
import returnRoutes from "./returns"
|
||||
import variantRoutes from "./variants"
|
||||
import collectionRoutes from "./collections"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -60,6 +61,7 @@ export default (app, container, config) => {
|
||||
swapRoutes(route)
|
||||
returnRoutes(route)
|
||||
variantRoutes(route)
|
||||
collectionRoutes(route)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ describe("POST /admin/products", () => {
|
||||
payload: {
|
||||
title: "Test Product",
|
||||
description: "Test Description",
|
||||
tags: "hi,med,dig",
|
||||
tags: [{ id: "test", value: "test" }],
|
||||
handle: "test-product",
|
||||
},
|
||||
adminSession: {
|
||||
@@ -36,7 +36,7 @@ describe("POST /admin/products", () => {
|
||||
expect(ProductServiceMock.create).toHaveBeenCalledWith({
|
||||
title: "Test Product",
|
||||
description: "Test Description",
|
||||
tags: "hi,med,dig",
|
||||
tags: [{ id: "test", value: "test" }],
|
||||
handle: "test-product",
|
||||
is_giftcard: false,
|
||||
profile_id: IdMap.getId("default_shipping_profile"),
|
||||
|
||||
@@ -34,11 +34,12 @@ describe("GET /admin/products/:id", () => {
|
||||
"title",
|
||||
"subtitle",
|
||||
"description",
|
||||
"tags",
|
||||
"handle",
|
||||
"is_giftcard",
|
||||
"thumbnail",
|
||||
"profile_id",
|
||||
"collection_id",
|
||||
"type_id",
|
||||
"weight",
|
||||
"length",
|
||||
"height",
|
||||
@@ -57,6 +58,9 @@ describe("GET /admin/products/:id", () => {
|
||||
"variants.options",
|
||||
"images",
|
||||
"options",
|
||||
"tags",
|
||||
"type",
|
||||
"collection",
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -6,13 +6,28 @@ export default async (req, res) => {
|
||||
title: Validator.string().required(),
|
||||
subtitle: Validator.string().allow(""),
|
||||
description: Validator.string().allow(""),
|
||||
tags: Validator.string().optional(),
|
||||
is_giftcard: Validator.boolean().default(false),
|
||||
images: Validator.array()
|
||||
.items(Validator.string())
|
||||
.optional(),
|
||||
thumbnail: Validator.string().optional(),
|
||||
handle: Validator.string().optional(),
|
||||
type: Validator.object()
|
||||
.keys({
|
||||
id: Validator.string().optional(),
|
||||
value: Validator.string().required(),
|
||||
})
|
||||
.allow(null)
|
||||
.optional(),
|
||||
collection_id: Validator.string()
|
||||
.allow(null)
|
||||
.optional(),
|
||||
tags: Validator.array()
|
||||
.items({
|
||||
id: Validator.string().optional(),
|
||||
value: Validator.string().required(),
|
||||
})
|
||||
.optional(),
|
||||
options: Validator.array().items({
|
||||
title: Validator.string().required(),
|
||||
}),
|
||||
@@ -48,7 +63,7 @@ export default async (req, res) => {
|
||||
Validator.object()
|
||||
.keys({
|
||||
region_id: Validator.string(),
|
||||
currency_code: Validator.string().required(),
|
||||
currency_code: Validator.string(),
|
||||
amount: Validator.number()
|
||||
.integer()
|
||||
.required(),
|
||||
|
||||
@@ -8,6 +8,11 @@ export default app => {
|
||||
|
||||
route.post("/", middlewares.wrap(require("./create-product").default))
|
||||
route.post("/:id", middlewares.wrap(require("./update-product").default))
|
||||
route.get("/types", middlewares.wrap(require("./list-types").default))
|
||||
route.get(
|
||||
"/tag-usage",
|
||||
middlewares.wrap(require("./list-tag-usage-count").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/variants",
|
||||
@@ -52,6 +57,9 @@ export const defaultRelations = [
|
||||
"variants.options",
|
||||
"images",
|
||||
"options",
|
||||
"tags",
|
||||
"type",
|
||||
"collection",
|
||||
]
|
||||
|
||||
export const defaultFields = [
|
||||
@@ -59,11 +67,12 @@ export const defaultFields = [
|
||||
"title",
|
||||
"subtitle",
|
||||
"description",
|
||||
"tags",
|
||||
"handle",
|
||||
"is_giftcard",
|
||||
"thumbnail",
|
||||
"profile_id",
|
||||
"collection_id",
|
||||
"type_id",
|
||||
"weight",
|
||||
"length",
|
||||
"height",
|
||||
@@ -82,11 +91,12 @@ export const allowedFields = [
|
||||
"title",
|
||||
"subtitle",
|
||||
"description",
|
||||
"tags",
|
||||
"handle",
|
||||
"is_giftcard",
|
||||
"thumbnail",
|
||||
"profile_id",
|
||||
"collection_id",
|
||||
"type_id",
|
||||
"weight",
|
||||
"length",
|
||||
"height",
|
||||
@@ -105,4 +115,7 @@ export const allowedRelations = [
|
||||
"variants.prices",
|
||||
"images",
|
||||
"options",
|
||||
"tags",
|
||||
"type",
|
||||
"collection",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export default async (req, res) => {
|
||||
try {
|
||||
const productService = req.scope.resolve("productService")
|
||||
|
||||
const tags = await productService.listTagsByUsage()
|
||||
|
||||
res.json({ tags })
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export default async (req, res) => {
|
||||
try {
|
||||
const productService = req.scope.resolve("productService")
|
||||
|
||||
const types = await productService.listTypes()
|
||||
|
||||
res.json({ types })
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,22 @@ export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
title: Validator.string().optional(),
|
||||
description: Validator.string().optional(),
|
||||
tags: Validator.string().optional(),
|
||||
type: Validator.object()
|
||||
.keys({
|
||||
id: Validator.string().optional(),
|
||||
value: Validator.string().required(),
|
||||
})
|
||||
.allow(null)
|
||||
.optional(),
|
||||
collection_id: Validator.string()
|
||||
.allow(null)
|
||||
.optional(),
|
||||
tags: Validator.array()
|
||||
.items({
|
||||
id: Validator.string().optional(),
|
||||
value: Validator.string().required(),
|
||||
})
|
||||
.optional(),
|
||||
handle: Validator.string().optional(),
|
||||
weight: Validator.number().optional(),
|
||||
length: Validator.number().optional(),
|
||||
|
||||
Reference in New Issue
Block a user