feat(medusa): Product category, type and tags

This commit is contained in:
Oliver Windall Juhl
2021-02-12 08:42:19 +01:00
committed by GitHub
parent 2a8b556256
commit c4d1203155
45 changed files with 1704 additions and 45 deletions
@@ -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(),