feat: list customer groups (#1099)

This commit is contained in:
Frane Polić
2022-03-02 11:38:44 +01:00
committed by GitHub
parent e844f4a5b7
commit a514d84ccf
11 changed files with 294 additions and 65 deletions

View File

@@ -466,6 +466,66 @@ describe("/admin/customer-groups", () => {
const db = useDb()
await db.teardown()
})
it("retreive a list of customer groups", async () => {
const api = useApi()
const response = await api
.get(
`/admin/customer-groups?limit=5&offset=2&expand=customers&order=created_at`,
{
headers: {
Authorization: "Bearer test_token",
},
}
)
.catch(console.log)
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(7)
expect(response.data.customer_groups.length).toEqual(5)
expect(response.data.customer_groups[0]).toEqual(
expect.objectContaining({ id: "customer-group-3" })
)
expect(response.data.customer_groups[0]).toHaveProperty("customers")
})
it("retreive a list of customer groups filtered by name using `q` param", async () => {
const api = useApi()
const response = await api.get(`/admin/customer-groups?q=vip-customers`, {
headers: {
Authorization: "Bearer test_token",
},
})
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(1)
expect(response.data.customer_groups).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: "customer-group-1" }),
])
)
expect(response.data.customer_groups[0]).not.toHaveProperty("customers")
})
})
describe("GET /admin/customer-groups/:id", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
await customerSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("gets customer group", async () => {
const api = useApi()

View File

@@ -276,7 +276,10 @@ describe("/admin/customers", () => {
expect(response.status).toEqual(200)
expect(response.data.customer.groups).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: "test-group-4", name: "test-group-4" }),
expect.objectContaining({
id: "test-group-4",
name: "test-group-4",
}),
])
)
@@ -323,8 +326,11 @@ describe("/admin/customers", () => {
expect(response.data.customer.groups.length).toEqual(2)
expect(response.data.customer.groups).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: "test-group-4", name: "test-group-4" }),
expect.objectContaining({ id: "test-group-5", name: "test-group-5" }),
expect.objectContaining({
id: "test-group-4",
name: "test-group-4",
}),
])
)
})

View File

@@ -36,6 +36,21 @@ module.exports = async (connection, data = {}) => {
has_account: true,
})
const customer5 = manager.create(Customer, {
id: "test-customer-5",
email: "test5@email.com",
})
const customer6 = manager.create(Customer, {
id: "test-customer-6",
email: "test6@email.com",
})
const customer7 = manager.create(Customer, {
id: "test-customer-7",
email: "test7@email.com",
})
const deletionCustomer = await manager.create(Customer, {
id: "test-customer-delete-cg",
email: "test-deletetion-cg@email.com",
@@ -55,7 +70,7 @@ module.exports = async (connection, data = {}) => {
await manager.insert(CustomerGroup, {
id: "customer-group-3",
name: "vest-group-3",
name: "test-group-3",
})
await manager.insert(CustomerGroup, {
@@ -63,24 +78,6 @@ module.exports = async (connection, data = {}) => {
name: "test-group-4",
})
const customer5 = manager.create(Customer, {
id: "test-customer-5",
email: "test5@email.com",
})
await manager.save(customer5)
const customer6 = manager.create(Customer, {
id: "test-customer-6",
email: "test6@email.com",
})
await manager.save(customer6)
const customer7 = manager.create(Customer, {
id: "test-customer-7",
email: "test7@email.com",
})
await manager.save(customer7)
const c_group_5 = manager.create(CustomerGroup, {
id: "test-group-5",
name: "test-group-5",

View File

@@ -8,16 +8,16 @@
"build": "babel src -d dist --extensions \".ts,.js\""
},
"dependencies": {
"@medusajs/medusa": "1.1.64-dev-1645628651544",
"@medusajs/medusa": "1.2.0-dev-1646213107704",
"faker": "^5.5.3",
"medusa-interfaces": "1.1.34-dev-1645628651544",
"medusa-interfaces": "1.2.0-dev-1646213107704",
"typeorm": "^0.2.31"
},
"devDependencies": {
"@babel/cli": "^7.12.10",
"@babel/core": "^7.12.10",
"@babel/node": "^7.12.10",
"babel-preset-medusa-package": "1.1.19-dev-1645441522984",
"babel-preset-medusa-package": "1.1.19-dev-1646213107704",
"jest": "^26.6.3"
}
}

View File

@@ -1256,10 +1256,10 @@
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
"@medusajs/medusa-cli@1.1.27-dev-1645628651544":
version "1.1.27-dev-1645628651544"
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.1.27-dev-1645628651544.tgz#0b5531d3c0df9a6a9ea01990b53f751692439458"
integrity sha512-3NrmJwIiUyLB/mYhVY0vUzrsFqHBCV+8LF+mrRwYWlsixXT8muSVUpObPJtvSRG4D/6xQrKcFk9wreDoT+Az9w==
"@medusajs/medusa-cli@1.2.0-dev-1646213107704":
version "1.2.0-dev-1646213107704"
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.2.0-dev-1646213107704.tgz#b84f9143450f3c03e732277b6c5bbeb0df761b2e"
integrity sha512-U1BqPe167vxpzf2YfopVpNTq0qcBkX3jkWFCNduHEjkBbuOXchlB/MheKtxIsxjHV1hzmjTr9uw3owu93pDYCQ==
dependencies:
"@babel/polyfill" "^7.8.7"
"@babel/runtime" "^7.9.6"
@@ -1277,8 +1277,8 @@
is-valid-path "^0.1.1"
joi-objectid "^3.0.1"
meant "^1.0.1"
medusa-core-utils "1.1.31-dev-1645628651544"
medusa-telemetry "0.0.11-dev-1645628651544"
medusa-core-utils "1.1.31-dev-1646213107704"
medusa-telemetry "0.0.11-dev-1646213107704"
netrc-parser "^3.1.6"
open "^8.0.6"
ora "^5.4.1"
@@ -1292,13 +1292,13 @@
winston "^3.3.3"
yargs "^15.3.1"
"@medusajs/medusa@1.1.64-dev-1645628651544":
version "1.1.64-dev-1645628651544"
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.1.64-dev-1645628651544.tgz#d4e314cc8511d337bf380cdf0d3714b3ab039298"
integrity sha512-h2A1V96bJzMn1pe0jhQ98J4b8tv1JUGUQV3vYNIjHLKrWQVVNoY52Rk8bQjaCyH9YoITbiLOSRTApTSUsqzpyQ==
"@medusajs/medusa@1.2.0-dev-1646213107704":
version "1.2.0-dev-1646213107704"
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.2.0-dev-1646213107704.tgz#40ed0efe4f8e242c620eb1a984f0904465576920"
integrity sha512-pZnoM/U6WF4IdKriX7lKaxlmt9nlK4WoQ4PtzLjvZemjNHKriLjsg8ugvuBOuuzZJe66O8XEAXcoPh2o/SmAeg==
dependencies:
"@hapi/joi" "^16.1.8"
"@medusajs/medusa-cli" "1.1.27-dev-1645628651544"
"@medusajs/medusa-cli" "1.2.0-dev-1646213107704"
"@types/lodash" "^4.14.168"
awilix "^4.2.3"
body-parser "^1.19.0"
@@ -1322,8 +1322,8 @@
joi "^17.3.0"
joi-objectid "^3.0.1"
jsonwebtoken "^8.5.1"
medusa-core-utils "1.1.31-dev-1645628651544"
medusa-test-utils "1.1.37-dev-1645628651544"
medusa-core-utils "1.1.31-dev-1646213107704"
medusa-test-utils "1.1.37-dev-1646213107704"
morgan "^1.9.1"
multer "^1.4.2"
passport "^0.4.0"
@@ -1947,10 +1947,10 @@ babel-preset-jest@^26.6.2:
babel-plugin-jest-hoist "^26.6.2"
babel-preset-current-node-syntax "^1.0.0"
babel-preset-medusa-package@1.1.19-dev-1645628651544:
version "1.1.19-dev-1645628651544"
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.19-dev-1645628651544.tgz#506bd183276d30458da4e2857459c6f38435d4b0"
integrity sha512-0XBmV1OuUDVIQccYKfU3Yj2gMYIeLEV8I/ugyVfgQ4AiYU7to97wFzdD8dXUTXd1I94gw4jbyZeZwflj0NoMhw==
babel-preset-medusa-package@1.1.19-dev-1646213107704:
version "1.1.19-dev-1646213107704"
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.19-dev-1646213107704.tgz#820201a6abfbdb95af623f1cae8981bb5ccddb8d"
integrity sha512-gTkFWvA/i+UmPSK8O0J4HBXE1tqQSD/D2p3ernvcvoaaXYhiLhNUrRRe9F8YXdkFT4tVXy2DKz/eQyUG1dsnnA==
dependencies:
"@babel/plugin-proposal-class-properties" "^7.12.1"
"@babel/plugin-proposal-decorators" "^7.12.1"
@@ -5140,25 +5140,25 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
medusa-core-utils@1.1.31-dev-1645628651544:
version "1.1.31-dev-1645628651544"
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.31-dev-1645628651544.tgz#bd6c5dc2aec5f9a109afef8eee22cbef1c77d63c"
integrity sha512-xkLY5QPcM1yjvhhSv39zJX9RimCMS7p8Y3j9ey6JrZYXBwzznzKsDPTlhZAdNKZuvGXVvnnmJXh563fvS8lGlg==
medusa-core-utils@1.1.31-dev-1646213107704:
version "1.1.31-dev-1646213107704"
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.31-dev-1646213107704.tgz#40ca7c1a4b290b1af9cf855cfc3320c23bbf3c53"
integrity sha512-P3D/jVSnx0YYEU8hTVqEow4Xht/BAHnHyc/HSbU5YT7PtCwEd+MY7+bvSAkhbVXNDCVsxyGTDA2nuofPS/c4RQ==
dependencies:
joi "^17.3.0"
joi-objectid "^3.0.1"
medusa-interfaces@1.1.34-dev-1645628651544:
version "1.1.34-dev-1645628651544"
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.1.34-dev-1645628651544.tgz#6d19acdef4019813eaa7554721cbc4f02fb16083"
integrity sha512-QsP0CtriL0avWFtSWtFoLx1oAcMtgMKAyCIfkkRQuPLYtM0o7Rc1l4cMnvYw9I8XPw+/RFQnc0CL3vJqDVzEUQ==
medusa-interfaces@1.2.0-dev-1646213107704:
version "1.2.0-dev-1646213107704"
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.2.0-dev-1646213107704.tgz#1a5310484afb5c1d127ba5775e08e3c0502e1488"
integrity sha512-3+Ve8f5a+dvdcRafOzAuisXk/vwY9fBXgshof8xYFgtDso2INwMHTNIEgUQrjMd8yXt2+EO8l9wlBeAr02eQ0A==
dependencies:
medusa-core-utils "1.1.31-dev-1645628651544"
medusa-core-utils "1.1.31-dev-1646213107704"
medusa-telemetry@0.0.11-dev-1645628651544:
version "0.0.11-dev-1645628651544"
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.11-dev-1645628651544.tgz#b1534c1e52d699f10cd66cdb270f04217d8aba56"
integrity sha512-+IkNVi75gogVyXTL4dDSYY+3g26B69yljQDH6XOqwNSb8OXU/XBxmv6EiHzjopESe82caXr8kUnyFWOyybp9VA==
medusa-telemetry@0.0.11-dev-1646213107704:
version "0.0.11-dev-1646213107704"
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.11-dev-1646213107704.tgz#7dc191fd263774a31795c2696ffe015a2b5f7443"
integrity sha512-BLKdzaU1sE9Eqnt3lmE6wkocOuC7wMT0l2OWc0aW6YTRRWDOffxOuANku1bZCIdVtbJ1q25UpeY2dwuD7lXhZw==
dependencies:
axios "^0.21.1"
axios-retry "^3.1.9"
@@ -5170,13 +5170,13 @@ medusa-telemetry@0.0.11-dev-1645628651544:
remove-trailing-slash "^0.1.1"
uuid "^8.3.2"
medusa-test-utils@1.1.37-dev-1645628651544:
version "1.1.37-dev-1645628651544"
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.37-dev-1645628651544.tgz#45103cf82131b41d073e17188f9b7f5985b4c083"
integrity sha512-56OByKGpTGnFQ6ThEadThovK5+h/24/CTBOvBCq/voOmwxsr89LBTwd9UB7wbubb6L/voMWSfmOsCb/7kUkq6w==
medusa-test-utils@1.1.37-dev-1646213107704:
version "1.1.37-dev-1646213107704"
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.37-dev-1646213107704.tgz#5fdb8298ac482f1d89475115d7727dd5227ac866"
integrity sha512-S42x4NaioWXPwQluO+iOYDwV0LUmr7q58XClFJA8j7Nw07IFKJU2PPHAAPrbpWQKpa2pdCBYdR0FUID4/VOfXw==
dependencies:
"@babel/plugin-transform-classes" "^7.9.5"
medusa-core-utils "1.1.31-dev-1645628651544"
medusa-core-utils "1.1.31-dev-1646213107704"
randomatic "^3.1.1"
merge-descriptors@1.0.1:

View File

@@ -3,7 +3,7 @@ import { IsNumber, IsOptional, IsString, ValidateNested } from "class-validator"
import _, { identity } from "lodash"
import {
defaultAdminCollectionsFields,
defaultAdminCollectionsRelations
defaultAdminCollectionsRelations,
} from "."
import ProductCollectionService from "../../../../services/product-collection"
import { DateComparisonOperator } from "../../../../types/common"

View File

@@ -4,7 +4,7 @@ import { validator } from "../../../../utils/validator"
import { defaultAdminCustomerGroupsRelations } from "."
/**
* @oas [get] /customer-group/{id}
* @oas [get] /customer-groups/{id}
* operationId: "GetCustomerGroupsGroup"
* summary: "Retrieve a CustomerGroup"
* description: "Retrieves a Customer Group."

View File

@@ -8,6 +8,7 @@ const route = Router()
export default (app) => {
app.use("/customer-groups", route)
route.get("/", middlewares.wrap(require("./list-customer-groups").default))
route.get("/:id", middlewares.wrap(require("./get-customer-group").default))
route.post("/", middlewares.wrap(require("./create-customer-group").default))
route.post(

View File

@@ -0,0 +1,109 @@
import { IsNumber, IsOptional, IsString } from "class-validator"
import { Type } from "class-transformer"
import omit from "lodash/omit"
import { validator } from "../../../../utils/validator"
import { CustomerGroupService } from "../../../../services"
import { CustomerGroup } from "../../../../models/customer-group"
import { FindConfig } from "../../../../types/common"
import { defaultAdminCustomerGroupsRelations } from "."
import { FilterableCustomerGroupProps } from "../../../../types/customer-groups"
/**
* @oas [get] /customer-groups
* operationId: "GetCustomerGroups"
* summary: "Retrieve a list of customer groups"
* description: "Retrieve a list of customer groups."
* x-authenticated: true
* parameters:
* - (query) q {string} Query used for searching user group names.
* - (query) offset {string} How many groups to skip in the result.
* - (query) id {string} Ids of the groups to search for.
* - (query) order {string} to retrieve customer groups in.
* - (query) created_at {DateComparisonOperator} Date comparison for when resulting customer group was created, i.e. less than, greater than etc.
* - (query) updated_at {DateComparisonOperator} Date comparison for when resulting ustomer group was updated, i.e. less than, greater than etc.
* - (query) offset {string} How many customer groups to skip in the result.
* - (query) limit {string} Limit the number of customer groups returned.
* - (query) expand {string} (Comma separated) Which fields should be expanded in each customer groups of the result.
* tags:
* - CustomerGroup
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* customerGroup:
* $ref: "#/components/schemas/customer_group"
*/
export default async (req, res) => {
const validated = await validator(AdminGetCustomerGroupsParams, req.query)
const customerGroupService: CustomerGroupService = req.scope.resolve(
"customerGroupService"
)
let expandFields: string[] = []
if (validated.expand) {
expandFields = validated.expand.split(",")
}
const listConfig: FindConfig<CustomerGroup> = {
relations: expandFields.length
? expandFields
: defaultAdminCustomerGroupsRelations,
skip: validated.offset,
take: validated.limit,
order: { created_at: "DESC" } as { [k: string]: "DESC" },
}
if (typeof validated.order !== "undefined") {
if (validated.order.startsWith("-")) {
const [, field] = validated.order.split("-")
listConfig.order = { [field]: "DESC" }
} else {
listConfig.order = { [validated.order]: "ASC" }
}
}
const filterableFields = omit(validated, [
"limit",
"offset",
"expand",
"order",
])
const [data, count] = await customerGroupService.listAndCount(
filterableFields,
listConfig
)
res.json({
count,
customer_groups: data,
offset: validated.offset,
limit: validated.limit,
})
}
export class AdminGetCustomerGroupsParams extends FilterableCustomerGroupProps {
@IsString()
@IsOptional()
order?: string
@IsNumber()
@IsOptional()
@Type(() => Number)
offset?: number = 0
@IsNumber()
@IsOptional()
@Type(() => Number)
limit?: number = 10
@IsString()
@IsOptional()
expand?: string
}

View File

@@ -1,15 +1,15 @@
import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import { DeepPartial, EntityManager } from "typeorm"
import { DeepPartial, EntityManager, ILike, SelectQueryBuilder } from "typeorm"
import { CustomerService } from "."
import { CustomerGroup } from ".."
import { CustomerGroupRepository } from "../repositories/customer-group"
import { FindConfig } from "../types/common"
import { formatException } from "../utils/exception-formatter"
import {
CustomerGroupUpdate,
FilterableCustomerGroupProps,
} from "../types/customer-groups"
import { formatException } from "../utils/exception-formatter"
type CustomerGroupConstructorProps = {
manager: EntityManager
@@ -226,6 +226,41 @@ class CustomerGroupService extends BaseService {
return await cgRepo.find(query)
}
/**
* Retrieve a list of customer groups and total count of records that match the query.
*
* @param {Object} selector - the query object for find
* @param {Object} config - the config to be used for find
* @return {Promise} the result of the find operation
*/
async listAndCount(
selector: FilterableCustomerGroupProps = {},
config: FindConfig<CustomerGroup>
): Promise<[CustomerGroup[], number]> {
const cgRepo: CustomerGroupRepository = this.manager_.getCustomRepository(
this.customerGroupRepository_
)
let q
if ("q" in selector) {
q = selector.q
delete selector.q
}
const query = this.buildQuery_(selector, config)
if (q) {
const where = query.where
delete where.name
query.where = (qb: SelectQueryBuilder<CustomerGroup>): void => {
qb.where(where).andWhere([{ name: ILike(`%${q}%`) }])
}
}
return await cgRepo.findAndCount(query)
}
/**
* Remove list of customers from a customergroup
*

View File

@@ -1,12 +1,33 @@
import { IsString, ValidateNested } from "class-validator"
import { Type } from "class-transformer"
import { IsArray, IsOptional, IsString, ValidateNested } from "class-validator"
import { IsType } from "../utils/validators/is-type"
import { StringComparisonOperator } from "./common"
import { DateComparisonOperator, StringComparisonOperator } from "./common"
export class FilterableCustomerGroupProps {
@IsOptional()
@ValidateNested()
@IsType([String, [String], StringComparisonOperator])
id?: string | string[] | StringComparisonOperator
@IsString()
@IsOptional()
q?: string
@IsOptional()
@IsArray()
@IsString({ each: true })
name?: string[]
@IsOptional()
@ValidateNested()
@Type(() => DateComparisonOperator)
updated_at?: DateComparisonOperator
@IsOptional()
@ValidateNested()
@Type(() => DateComparisonOperator)
created_at?: DateComparisonOperator
}
export class CustomerGroupsBatchCustomer {