Adds endpoints to manage customers (#54)
This commit is contained in:
@@ -3,10 +3,6 @@ export default async (req, res) => {
|
||||
try {
|
||||
const userService = req.scope.resolve("userService")
|
||||
const token = await userService.generateResetPasswordToken(user_id)
|
||||
if (!token) {
|
||||
res.sendStatus(404)
|
||||
return
|
||||
}
|
||||
res.json(token)
|
||||
} catch (error) {
|
||||
throw error
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import jwt from "jsonwebtoken"
|
||||
import { Validator } from "medusa-core-utils"
|
||||
import config from "../../../../config"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { body } = req
|
||||
const schema = Validator.object().keys({
|
||||
email: Validator.string().required(),
|
||||
password: Validator.string().required(),
|
||||
})
|
||||
const { value, error } = schema.validate(body)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
const authService = req.scope.resolve("authService")
|
||||
const result = await authService.authenticateCustomer(
|
||||
value.email,
|
||||
value.password
|
||||
)
|
||||
if (!result.success) {
|
||||
res.sendStatus(401)
|
||||
return
|
||||
}
|
||||
|
||||
// Add JWT to cookie
|
||||
req.session.jwt = jwt.sign(
|
||||
{ customer_id: result.user._id },
|
||||
config.jwtSecret,
|
||||
{
|
||||
expiresIn: "30d",
|
||||
}
|
||||
)
|
||||
|
||||
res.json(result.customer)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { CustomerServiceMock } from "../../../../../services/__mocks__/customer"
|
||||
|
||||
describe("POST /store/customers", () => {
|
||||
describe("successfully creates a customer", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request("POST", `/store/customers`, {
|
||||
payload: {
|
||||
email: "lebron@james.com",
|
||||
first_name: "LeBron",
|
||||
last_name: "James",
|
||||
password: "TheGame",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls CustomerService create", () => {
|
||||
expect(CustomerServiceMock.create).toHaveBeenCalledTimes(1)
|
||||
expect(CustomerServiceMock.create).toHaveBeenCalledWith({
|
||||
email: "lebron@james.com",
|
||||
first_name: "LeBron",
|
||||
last_name: "James",
|
||||
password: "TheGame",
|
||||
})
|
||||
})
|
||||
|
||||
it("returns customer decorated", () => {
|
||||
expect(subject.body.email).toEqual("lebron@james.com")
|
||||
expect(subject.body.decorated).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fails if missing field", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request("POST", `/store/customers`, {
|
||||
payload: {
|
||||
first_name: "LeBron",
|
||||
last_name: "James",
|
||||
password: "TheGame",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("returns product decorated", () => {
|
||||
expect(subject.body.name).toEqual("invalid_data")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import jwt from "jsonwebtoken"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { CustomerServiceMock } from "../../../../../services/__mocks__/customer"
|
||||
|
||||
describe("POST /store/customers/password-token", () => {
|
||||
describe("successfully creates a customer", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request("POST", `/store/customers/password-token`, {
|
||||
payload: {
|
||||
email: "lebron@james.com",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls CustomerService retrieve", () => {
|
||||
expect(CustomerServiceMock.retrieveByEmail).toHaveBeenCalledTimes(1)
|
||||
expect(CustomerServiceMock.retrieveByEmail).toHaveBeenCalledWith(
|
||||
"lebron@james.com"
|
||||
)
|
||||
})
|
||||
|
||||
it("calls CustomerService retrieve", () => {
|
||||
expect(
|
||||
CustomerServiceMock.generateResetPasswordToken
|
||||
).toHaveBeenCalledTimes(1)
|
||||
expect(
|
||||
CustomerServiceMock.generateResetPasswordToken
|
||||
).toHaveBeenCalledWith(IdMap.getId("lebron"))
|
||||
})
|
||||
|
||||
it("returns customer decorated", () => {
|
||||
expect(subject.status).toEqual(204)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,59 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import jwt from "jsonwebtoken"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { CustomerServiceMock } from "../../../../../services/__mocks__/customer"
|
||||
|
||||
describe("POST /store/customers/password-reset", () => {
|
||||
describe("successfully creates a customer", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request("POST", `/store/customers/password-reset`, {
|
||||
payload: {
|
||||
email: "lebron@james.com",
|
||||
token: jwt.sign({ customer_id: IdMap.getId("lebron") }, "1234"),
|
||||
password: "TheGame",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls CustomerService create", () => {
|
||||
expect(CustomerServiceMock.update).toHaveBeenCalledTimes(1)
|
||||
expect(CustomerServiceMock.update).toHaveBeenCalledWith(
|
||||
IdMap.getId("lebron"),
|
||||
{
|
||||
password: "TheGame",
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns customer decorated", () => {
|
||||
expect(subject.body.email).toEqual("lebron@james.com")
|
||||
expect(subject.body.decorated).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fails if id in webtoken not matching", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request("POST", `/store/customers/password-reset`, {
|
||||
payload: {
|
||||
email: "lebron@james.com",
|
||||
token: jwt.sign({ customer_id: IdMap.getId("not-lebron") }, "1234"),
|
||||
password: "TheGame",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("fails", () => {
|
||||
expect(subject.status).toEqual(401)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,75 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { CustomerServiceMock } from "../../../../../services/__mocks__/customer"
|
||||
|
||||
describe("POST /store/customers/:id", () => {
|
||||
describe("successfully updates a customer", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/store/customers/${IdMap.getId("lebron")}`,
|
||||
{
|
||||
payload: {
|
||||
first_name: "LeBron",
|
||||
last_name: "James",
|
||||
},
|
||||
clientSession: {
|
||||
jwt: {
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls CustomerService create", () => {
|
||||
expect(CustomerServiceMock.update).toHaveBeenCalledTimes(1)
|
||||
expect(CustomerServiceMock.update).toHaveBeenCalledWith(
|
||||
IdMap.getId("lebron"),
|
||||
{
|
||||
first_name: "LeBron",
|
||||
last_name: "James",
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns product decorated", () => {
|
||||
expect(subject.body.first_name).toEqual("LeBron")
|
||||
expect(subject.body.decorated).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fails if not authenticated", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/store/customers/${IdMap.getId("customer1")}`,
|
||||
{
|
||||
payload: {
|
||||
first_name: "LeBron",
|
||||
last_name: "James",
|
||||
},
|
||||
clientSession: {
|
||||
jwt: {
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("status code 400", () => {
|
||||
expect(subject.status).toEqual(400)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,73 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { CustomerServiceMock } from "../../../../../services/__mocks__/customer"
|
||||
|
||||
describe("POST /store/customers/:id/password", () => {
|
||||
describe("successfully updates a customer", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/store/customers/${IdMap.getId("lebron")}/password`,
|
||||
{
|
||||
payload: {
|
||||
password: "NewPass",
|
||||
},
|
||||
clientSession: {
|
||||
jwt: {
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls CustomerService update", () => {
|
||||
expect(CustomerServiceMock.update).toHaveBeenCalledTimes(1)
|
||||
expect(CustomerServiceMock.update).toHaveBeenCalledWith(
|
||||
IdMap.getId("lebron"),
|
||||
{
|
||||
password: "NewPass",
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns product decorated", () => {
|
||||
expect(subject.body.first_name).toEqual("LeBron")
|
||||
expect(subject.body.decorated).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fails if not authenticated", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/store/customers/${IdMap.getId("customer1")}/password`,
|
||||
{
|
||||
payload: {
|
||||
first_name: "LeBron",
|
||||
last_name: "James",
|
||||
},
|
||||
clientSession: {
|
||||
jwt: {
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("status code 400", () => {
|
||||
expect(subject.status).toEqual(400)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res, next, id) => {
|
||||
if (!(req.user && req.user.customer_id === id)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"You must be logged in to update"
|
||||
)
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
email: Validator.string()
|
||||
.email()
|
||||
.required(),
|
||||
first_name: Validator.string().required(),
|
||||
last_name: Validator.string().required(),
|
||||
password: Validator.string().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
try {
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
const customer = await customerService.create(value)
|
||||
|
||||
const data = await customerService.decorate(customer)
|
||||
res.status(201).json(data)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Router } from "express"
|
||||
import middlewares from "../../../middlewares"
|
||||
|
||||
const route = Router()
|
||||
|
||||
export default app => {
|
||||
app.use("/customers", route)
|
||||
|
||||
route.post("/", middlewares.wrap(require("./create-customer").default))
|
||||
route.post(
|
||||
"/password-reset",
|
||||
middlewares.wrap(require("./reset-password").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/password-token",
|
||||
middlewares.wrap(require("./reset-password-token").default)
|
||||
)
|
||||
|
||||
// Authenticated endpoints
|
||||
route.use(middlewares.authenticate())
|
||||
|
||||
route.param("id", middlewares.wrap(require("./authorize-customer").default))
|
||||
|
||||
route.post("/:id", middlewares.wrap(require("./update-customer").default))
|
||||
route.post(
|
||||
"/:id/password",
|
||||
middlewares.wrap(require("./update-password").default)
|
||||
)
|
||||
return app
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
email: Validator.string()
|
||||
.email()
|
||||
.required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
const customer = await customerService.retrieveByEmail(value.email)
|
||||
|
||||
await customerService.generateResetPasswordToken(customer._id)
|
||||
|
||||
res.sendStatus(204)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import jwt from "jsonwebtoken"
|
||||
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
email: Validator.string()
|
||||
.email()
|
||||
.required(),
|
||||
token: Validator.string().required(),
|
||||
password: Validator.string().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
const customer = await customerService.retrieveByEmail(value.email)
|
||||
|
||||
const decodedToken = await jwt.verify(value.token, customer.password_hash)
|
||||
if (!decodedToken || decodedToken.customer_id !== customer._id) {
|
||||
res.status(401).send("Invalid or expired password reset token")
|
||||
}
|
||||
|
||||
await customerService.update(customer._id, { password: value.password })
|
||||
|
||||
const updated = await customerService.retrieve(customer._id)
|
||||
const data = await customerService.decorate(customer)
|
||||
res.status(200).json(data)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
first_name: Validator.string(),
|
||||
last_name: Validator.string(),
|
||||
password: Validator.string(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
await customerService.update(id, value)
|
||||
|
||||
const customer = await customerService.retrieve(id)
|
||||
const data = await customerService.decorate(customer)
|
||||
res.status(200).json(data)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
password: Validator.string().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
|
||||
await customerService.update(id, value)
|
||||
|
||||
const customer = await customerService.retrieve(id)
|
||||
const data = await customerService.decorate(customer)
|
||||
res.status(200).json(data)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Router } from "express"
|
||||
|
||||
import productRoutes from "./products"
|
||||
import cartRoutes from "./carts"
|
||||
import customerRoutes from "./customers"
|
||||
import shippingOptionRoutes from "./shipping-options"
|
||||
|
||||
const route = Router()
|
||||
@@ -8,6 +10,7 @@ const route = Router()
|
||||
export default app => {
|
||||
app.use("/store", route)
|
||||
|
||||
customerRoutes(route)
|
||||
productRoutes(route)
|
||||
cartRoutes(route)
|
||||
shippingOptionRoutes(route)
|
||||
|
||||
Reference in New Issue
Block a user