From dacc9c6c4bc40a385b869fe4fc023bf76e424875 Mon Sep 17 00:00:00 2001 From: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com> Date: Thu, 10 Mar 2022 13:05:46 +0100 Subject: [PATCH] Feat: Add groups to list customer (#1113) * add controllers directory and loaders * update integration tests * start groups * move controllers to pure functions * group filtering for customers * print errors * remove verbose flag * controller refactor * update imports * Feat/list customers by customer group (#1114) * move controllers to pure functions * add customer group test * add api for list customer group customers * remove unused imports * controller refactor * update imports * Update packages/medusa/src/api/routes/admin/customer-groups/get-customer-group-customers.ts Co-authored-by: Sebastian Rindom Co-authored-by: Sebastian Rindom * fix: dedupe Co-authored-by: Sebastian Rindom --- .../api/__tests__/admin/customer-groups.js | 47 +++++++++++++++++++ .../api/__tests__/admin/customer.js | 30 ++++++++++++ .../api/__tests__/taxes/admin-tax-rates.js | 2 +- .../get-customer-group-customers.ts | 33 +++++++++++++ .../api/routes/admin/customer-groups/index.ts | 6 +++ .../src/api/routes/admin/customers/index.ts | 6 ++- .../routes/admin/customers/list-customers.ts | 1 + packages/medusa/src/repositories/customer.ts | 26 +++++++++- packages/medusa/src/services/customer.js | 4 +- packages/medusa/src/types/customers.ts | 4 ++ 10 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 packages/medusa/src/api/routes/admin/customer-groups/get-customer-group-customers.ts diff --git a/integration-tests/api/__tests__/admin/customer-groups.js b/integration-tests/api/__tests__/admin/customer-groups.js index 4076f8e607..5671014c58 100644 --- a/integration-tests/api/__tests__/admin/customer-groups.js +++ b/integration-tests/api/__tests__/admin/customer-groups.js @@ -193,6 +193,53 @@ describe("/admin/customer-groups", () => { }) }) + describe("GET /admin/customer-groups/{id}/customers", () => { + 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("lists customers in group and count", async () => { + const api = useApi() + + const response = await api + .get("/admin/customer-groups/test-group-5/customers", { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err) + }) + + expect(response.status).toEqual(200) + expect(response.data.count).toEqual(3) + expect(response.data.customers).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "test-customer-5", + }), + expect.objectContaining({ + id: "test-customer-6", + }), + expect.objectContaining({ + id: "test-customer-7", + }), + ]) + ) + }) + }) + describe("POST /admin/customer-groups/{id}/customers/batch", () => { beforeEach(async () => { try { diff --git a/integration-tests/api/__tests__/admin/customer.js b/integration-tests/api/__tests__/admin/customer.js index f4bcb20b75..46540422a2 100644 --- a/integration-tests/api/__tests__/admin/customer.js +++ b/integration-tests/api/__tests__/admin/customer.js @@ -75,6 +75,36 @@ describe("/admin/customers", () => { ) }) + it("lists customers in group and count", async () => { + const api = useApi() + + const response = await api + .get("/admin/customers?groups[]=test-group-5", { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err) + }) + + expect(response.status).toEqual(200) + expect(response.data.count).toEqual(3) + expect(response.data.customers).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "test-customer-5", + }), + expect.objectContaining({ + id: "test-customer-6", + }), + expect.objectContaining({ + id: "test-customer-7", + }), + ]) + ) + }) + it("lists customers with specific query", async () => { const api = useApi() diff --git a/integration-tests/api/__tests__/taxes/admin-tax-rates.js b/integration-tests/api/__tests__/taxes/admin-tax-rates.js index 637062926b..f76932ebe8 100644 --- a/integration-tests/api/__tests__/taxes/admin-tax-rates.js +++ b/integration-tests/api/__tests__/taxes/admin-tax-rates.js @@ -216,7 +216,7 @@ describe("/admin/tax-rates", () => { ) }) - test.only("fails with 404 on unknown rate", async () => { + test("fails with 404 on unknown rate", async () => { await adminSeeder(dbConnection) const { tax_rates } = await createTaxRates(dbConnection, 1, 1, 200) const [rate] = tax_rates diff --git a/packages/medusa/src/api/routes/admin/customer-groups/get-customer-group-customers.ts b/packages/medusa/src/api/routes/admin/customer-groups/get-customer-group-customers.ts new file mode 100644 index 0000000000..b8bbda49bb --- /dev/null +++ b/packages/medusa/src/api/routes/admin/customer-groups/get-customer-group-customers.ts @@ -0,0 +1,33 @@ +import CustomerController from "../../../../controllers/customers" + +/** + * @oas [get] /customer-groups/{id}/customers + * operationId: "GetCustomerGroupsGroupCustomers" + * summary: "List Customers" + * description: "Retrieves a list of Customers." + * x-authenticated: true + * tags: + * - Customer + * responses: + * 200: + * description: OK + * content: + * application/json: + * schema: + * properties: + * customer: + * $ref: "#/components/schemas/customer" + */ +export default async (req, res) => { + const { id } = req.params + + req.query.groups = [id] + + const result = await CustomerController.listAndCount( + req.scope, + req.query, + req.body + ) + + res.json(result) +} diff --git a/packages/medusa/src/api/routes/admin/customer-groups/index.ts b/packages/medusa/src/api/routes/admin/customer-groups/index.ts index 2db730d993..f6418e78f3 100644 --- a/packages/medusa/src/api/routes/admin/customer-groups/index.ts +++ b/packages/medusa/src/api/routes/admin/customer-groups/index.ts @@ -24,6 +24,12 @@ export default (app) => { "/:id", middlewares.wrap(require("./delete-customer-group").default) ) + + route.get( + "/:id/customers", + middlewares.wrap(require("./get-customer-group-customers").default) + ) + route.post( "/:id", middlewares.wrap(require("./update-customer-group").default) diff --git a/packages/medusa/src/api/routes/admin/customers/index.ts b/packages/medusa/src/api/routes/admin/customers/index.ts index d6bbd32ef7..955c40ccb5 100644 --- a/packages/medusa/src/api/routes/admin/customers/index.ts +++ b/packages/medusa/src/api/routes/admin/customers/index.ts @@ -8,7 +8,11 @@ const route = Router() export default (app) => { app.use("/customers", route) - route.get("/", middlewares.wrap(require("./list-customers").default)) + route.get( + "/", + middlewares.normalizeQuery(), + middlewares.wrap(require("./list-customers").default) + ) route.get("/:id", middlewares.wrap(require("./get-customer").default)) route.post("/", middlewares.wrap(require("./create-customer").default)) diff --git a/packages/medusa/src/api/routes/admin/customers/list-customers.ts b/packages/medusa/src/api/routes/admin/customers/list-customers.ts index 947bdd7c45..663912d7f0 100644 --- a/packages/medusa/src/api/routes/admin/customers/list-customers.ts +++ b/packages/medusa/src/api/routes/admin/customers/list-customers.ts @@ -36,6 +36,7 @@ export class AdminGetCustomersParams extends AdminListCustomerSelector { @Type(() => Number) limit = 50 + @IsOptional() @IsNumber() @IsOptional() @Type(() => Number) diff --git a/packages/medusa/src/repositories/customer.ts b/packages/medusa/src/repositories/customer.ts index 6a4838c740..3a362d888d 100644 --- a/packages/medusa/src/repositories/customer.ts +++ b/packages/medusa/src/repositories/customer.ts @@ -2,4 +2,28 @@ import { EntityRepository, Repository } from "typeorm" import { Customer } from "../models/customer" @EntityRepository(Customer) -export class CustomerRepository extends Repository {} +export class CustomerRepository extends Repository { + async listAndCount(query): Promise<[Customer[], number]> { + const groups = query?.where?.groups + delete query?.where?.groups + + let qb = this.createQueryBuilder("customer") + .where(query.where) + .skip(query.skip) + .take(query.take) + + if (groups) { + qb = qb + .leftJoinAndSelect("customer.groups", "group") + .andWhere(`group.id IN (:...ids)`, { ids: groups.value }) + } + + if (query.relations?.length) { + query.relations.forEach((rel) => { + qb = qb.leftJoinAndSelect(`customer.${rel}`, rel) + }) + } + + return await qb.getManyAndCount() + } +} diff --git a/packages/medusa/src/services/customer.js b/packages/medusa/src/services/customer.js index ee4f25f05c..0874eec46d 100644 --- a/packages/medusa/src/services/customer.js +++ b/packages/medusa/src/services/customer.js @@ -199,7 +199,6 @@ class CustomerService extends BaseService { query.where = (qb) => { qb.where(where) - qb.andWhere( new Brackets((qb) => { qb.where({ email: ILike(`%${q}%`) }) @@ -210,8 +209,7 @@ class CustomerService extends BaseService { } } - const [customers, count] = await customerRepo.findAndCount(query) - return [customers, count] + return await customerRepo.listAndCount(query) } /** diff --git a/packages/medusa/src/types/customers.ts b/packages/medusa/src/types/customers.ts index 965bfd15da..9bbfd4619e 100644 --- a/packages/medusa/src/types/customers.ts +++ b/packages/medusa/src/types/customers.ts @@ -4,4 +4,8 @@ export class AdminListCustomerSelector { @IsString() @IsOptional() q?: string + + @IsOptional() + @IsString({ each: true }) + groups?: string[] }