diff --git a/integration-tests/api/__tests__/admin/customer.js b/integration-tests/api/__tests__/admin/customer.js new file mode 100644 index 0000000000..14c4222977 --- /dev/null +++ b/integration-tests/api/__tests__/admin/customer.js @@ -0,0 +1,135 @@ +const { dropDatabase } = require("pg-god"); +const path = require("path"); + +const setupServer = require("../../../helpers/setup-server"); +const { useApi } = require("../../../helpers/use-api"); +const { initDb } = require("../../../helpers/use-db"); + +const customerSeeder = require("../../helpers/customer-seeder"); +const adminSeeder = require("../../helpers/admin-seeder"); + +jest.setTimeout(30000); + +describe("/admin/customers", () => { + let medusaProcess; + let dbConnection; + + beforeAll(async () => { + const cwd = path.resolve(path.join(__dirname, "..", "..")); + dbConnection = await initDb({ cwd }); + medusaProcess = await setupServer({ cwd }); + }); + + afterAll(async () => { + await dbConnection.close(); + await dropDatabase({ databaseName: "medusa-integration" }); + + medusaProcess.kill(); + }); + + describe("GET /admin/customers", () => { + beforeEach(async () => { + try { + await adminSeeder(dbConnection); + await customerSeeder(dbConnection); + } catch (err) { + console.log(err); + throw err; + } + }); + + afterEach(async () => { + const manager = dbConnection.manager; + await manager.query(`DELETE FROM "address"`); + await manager.query(`DELETE FROM "customer"`); + await manager.query(`DELETE FROM "user"`); + }); + + it("lists customers and query count", async () => { + const api = useApi(); + + const response = await api + .get("/admin/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-1", + }), + expect.objectContaining({ + id: "test-customer-2", + }), + expect.objectContaining({ + id: "test-customer-3", + }), + ]) + ); + }); + + it("lists customers with specific query", async () => { + const api = useApi(); + + const response = await api + .get("/admin/customers?q=test2@email.com", { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err); + }); + + expect(response.status).toEqual(200); + expect(response.data.count).toEqual(1); + expect(response.data.customers).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "test-customer-2", + email: "test2@email.com", + }), + ]) + ); + }); + + it("lists customers with expand query", async () => { + const api = useApi(); + + const response = await api + .get("/admin/customers?q=test1@email.com&expand=shipping_addresses", { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err); + }); + + expect(response.status).toEqual(200); + expect(response.data.count).toEqual(1); + console.log(response.data.customers); + expect(response.data.customers).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "test-customer-1", + shipping_addresses: expect.arrayContaining([ + expect.objectContaining({ + id: "test-address", + first_name: "Lebron", + last_name: "James", + }), + ]), + }), + ]) + ); + }); + }); +}); diff --git a/integration-tests/api/helpers/customer-seeder.js b/integration-tests/api/helpers/customer-seeder.js new file mode 100644 index 0000000000..78b3e22ec5 --- /dev/null +++ b/integration-tests/api/helpers/customer-seeder.js @@ -0,0 +1,27 @@ +const { Customer, Address } = require("@medusajs/medusa"); + +module.exports = async (connection, data = {}) => { + const manager = connection.manager; + + await manager.insert(Customer, { + id: "test-customer-1", + email: "test1@email.com", + }); + + await manager.insert(Customer, { + id: "test-customer-2", + email: "test2@email.com", + }); + + await manager.insert(Customer, { + id: "test-customer-3", + email: "test3@email.com", + }); + + await manager.insert(Address, { + id: "test-address", + first_name: "Lebron", + last_name: "James", + customer_id: "test-customer-1", + }); +}; diff --git a/packages/medusa/src/api/routes/admin/customers/list-customers.js b/packages/medusa/src/api/routes/admin/customers/list-customers.js index 6683f80e06..e0bcb9b9c8 100644 --- a/packages/medusa/src/api/routes/admin/customers/list-customers.js +++ b/packages/medusa/src/api/routes/admin/customers/list-customers.js @@ -2,18 +2,32 @@ export default async (req, res) => { try { const customerService = req.scope.resolve("customerService") - const limit = parseInt(req.query.limit) || 10 + const limit = parseInt(req.query.limit) || 50 const offset = parseInt(req.query.offset) || 0 + const selector = {} + + if ("q" in req.query) { + selector.q = req.query.q + } + + let expandFields = [] + if ("expand" in req.query) { + expandFields = req.query.expand.split(",") + } + const listConfig = { - relations: [], + relations: expandFields.length ? expandFields : [], skip: offset, take: limit, } - const customers = await customerService.list({}, listConfig) + const [customers, count] = await customerService.listAndCount( + selector, + listConfig + ) - res.json({ customers, count: customers.length, offset, limit }) + res.json({ customers, count, offset, limit }) } catch (error) { throw error } diff --git a/packages/medusa/src/services/customer.js b/packages/medusa/src/services/customer.js index 6db5092f03..486e89c210 100644 --- a/packages/medusa/src/services/customer.js +++ b/packages/medusa/src/services/customer.js @@ -3,6 +3,7 @@ import Scrypt from "scrypt-kdf" import _ from "lodash" import { Validator, MedusaError } from "medusa-core-utils" import { BaseService } from "medusa-interfaces" +import { Brackets } from "typeorm" /** * Provides layer to manipulate customers. @@ -132,6 +133,50 @@ class CustomerService extends BaseService { return customerRepo.find(query) } + async listAndCount( + selector, + config = { relations: [], skip: 0, take: 50, order: { created_at: "DESC" } } + ) { + const customerRepo = this.manager_.getCustomRepository( + this.customerRepository_ + ) + + 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.email + delete where.first_name + delete where.last_name + + query.join = { + alias: "customer", + } + + query.where = qb => { + qb.where(where) + + qb.andWhere( + new Brackets(qb => { + qb.where(`customer.first_name ILIKE :q`, { q: `%${q}%` }) + .orWhere(`customer.last_name ILIKE :q`, { q: `%${q}%` }) + .orWhere(`customer.email ILIKE :q`, { q: `%${q}%` }) + }) + ) + } + } + + const [customers, count] = await customerRepo.findAndCount(query) + return [customers, count] + } + /** * Return the total number of documents in database * @return {Promise} the result of the count operation