Adds admin endpoints / routes for user (#36)
Adds all endpoints / routes for user operations Tweaks in UserService Adds update in UserService Fixes old tests
This commit is contained in:
1 parent
e3cdde3c9e
commit
a53822d298
27 files changed
+718
-22
No files matched your search
@@ -1,14 +1,12 @@
|
|||||||
import { Router } from "express"
|
import { Router } from "express"
|
||||||
import admin from "./routes/admin"
|
import admin from "./routes/admin"
|
||||||
import store from "./routes/store"
|
import store from "./routes/store"
|
||||||
import users from "./routes/users"
|
|
||||||
import errorHandler from "./middlewares/error-handler"
|
import errorHandler from "./middlewares/error-handler"
|
||||||
|
|
||||||
// guaranteed to get dependencies
|
// guaranteed to get dependencies
|
||||||
export default () => {
|
export default () => {
|
||||||
const app = Router()
|
const app = Router()
|
||||||
|
|
||||||
users(app)
|
|
||||||
admin(app)
|
admin(app)
|
||||||
store(app)
|
store(app)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Router } from "express"
|
|||||||
import middlewares from "../../middlewares"
|
import middlewares from "../../middlewares"
|
||||||
import authRoutes from "./auth"
|
import authRoutes from "./auth"
|
||||||
import productRoutes from "./products"
|
import productRoutes from "./products"
|
||||||
|
import userRoutes from "./users"
|
||||||
import productVariantRoutes from "./product-variants"
|
import productVariantRoutes from "./product-variants"
|
||||||
import regionRoutes from "./regions"
|
import regionRoutes from "./regions"
|
||||||
import shippingOptionRoutes from "./shipping-options"
|
import shippingOptionRoutes from "./shipping-options"
|
||||||
@@ -20,6 +21,7 @@ export default app => {
|
|||||||
route.use(middlewares.authenticate())
|
route.use(middlewares.authenticate())
|
||||||
|
|
||||||
productRoutes(route)
|
productRoutes(route)
|
||||||
|
userRoutes(route)
|
||||||
regionRoutes(route)
|
regionRoutes(route)
|
||||||
shippingOptionRoutes(route)
|
shippingOptionRoutes(route)
|
||||||
shippingProfileRoutes(route)
|
shippingProfileRoutes(route)
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { UserServiceMock } from "../../../../../services/__mocks__/user"
|
||||||
|
|
||||||
|
describe("POST /admin/users", () => {
|
||||||
|
describe("successfully creates a user", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("POST", "/admin/users", {
|
||||||
|
payload: {
|
||||||
|
email: "oliver@test.dk",
|
||||||
|
password: "123456789",
|
||||||
|
},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls UserService create", () => {
|
||||||
|
expect(UserServiceMock.create).toHaveBeenCalledTimes(1)
|
||||||
|
expect(UserServiceMock.create).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
email: "oliver@test.dk",
|
||||||
|
},
|
||||||
|
"123456789"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the user", () => {
|
||||||
|
expect(subject.body._id).toEqual(IdMap.getId("test-user"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("handles failed create operation", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("POST", "/admin/users", {
|
||||||
|
payload: {
|
||||||
|
email: "olivertest.dk",
|
||||||
|
password: "123456789",
|
||||||
|
},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 400 on invalid email", () => {
|
||||||
|
expect(subject.status).toEqual(400)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { UserServiceMock } from "../../../../../services/__mocks__/user"
|
||||||
|
|
||||||
|
describe("DELETE /admin/users/:id", () => {
|
||||||
|
describe("successfully deletes a user", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"DELETE",
|
||||||
|
`/admin/users/${IdMap.getId("delete-user")}`,
|
||||||
|
{
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls UserService create", () => {
|
||||||
|
expect(UserServiceMock.delete).toHaveBeenCalledTimes(1)
|
||||||
|
expect(UserServiceMock.delete).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("delete-user")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns correct delete data", () => {
|
||||||
|
expect(subject.body).toEqual({
|
||||||
|
id: IdMap.getId("delete-user"),
|
||||||
|
object: "user",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { UserServiceMock } from "../../../../../services/__mocks__/user"
|
||||||
|
|
||||||
|
describe("GET /admin/users/:id", () => {
|
||||||
|
describe("successfully gets a user", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"GET",
|
||||||
|
`/admin/users/${IdMap.getId("test-user")}`,
|
||||||
|
{
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls UserService retrieve", () => {
|
||||||
|
expect(UserServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||||
|
expect(UserServiceMock.retrieve).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("test-user")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the user", () => {
|
||||||
|
expect(subject.body._id).toEqual(IdMap.getId("test-user"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { UserServiceMock } from "../../../../../services/__mocks__/user"
|
||||||
|
|
||||||
|
describe("GET /admin/users", () => {
|
||||||
|
describe("successful retrieval", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("GET", `/admin/users`, {
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls service retrieve", () => {
|
||||||
|
expect(UserServiceMock.list).toHaveBeenCalledTimes(1)
|
||||||
|
expect(UserServiceMock.list).toHaveBeenCalledWith({})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { UserServiceMock } from "../../../../../services/__mocks__/user"
|
||||||
|
|
||||||
|
describe("POST /admin/users/:id/reset-password-token", () => {
|
||||||
|
describe("successfully generates reset password token", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/admin/users/${IdMap.getId("test-user")}/reset-password-token`,
|
||||||
|
{
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls UserService generateResetPasswordToken", () => {
|
||||||
|
expect(UserServiceMock.generateResetPasswordToken).toHaveBeenCalledTimes(
|
||||||
|
1
|
||||||
|
)
|
||||||
|
expect(UserServiceMock.generateResetPasswordToken).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("test-user")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import jwt from "jsonwebtoken"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { UserServiceMock } from "../../../../../services/__mocks__/user"
|
||||||
|
|
||||||
|
describe("POST /admin/users/:id/reset-password", () => {
|
||||||
|
describe("successfully resets password", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls UserService setPassword", async () => {
|
||||||
|
const exp = Math.floor(Date.now() / 1000) + 60 * 15
|
||||||
|
const token = jwt.sign(
|
||||||
|
{
|
||||||
|
userId: "test-user-id",
|
||||||
|
name: "Oliver Juhl",
|
||||||
|
email: "oliver@test.dk",
|
||||||
|
exp,
|
||||||
|
},
|
||||||
|
"123456789hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/admin/users/test-user-id/reset-password`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
token,
|
||||||
|
password: "new-password",
|
||||||
|
},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(UserServiceMock.setPassword).toHaveBeenCalledTimes(1)
|
||||||
|
expect(UserServiceMock.setPassword).toHaveBeenCalledWith(
|
||||||
|
"test-user-id",
|
||||||
|
"new-password"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns updated user", () => {
|
||||||
|
expect(subject.body._id).toEqual("test-user-id")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { UserServiceMock } from "../../../../../services/__mocks__/user"
|
||||||
|
|
||||||
|
describe("POST /admin/users/:id/set-password", () => {
|
||||||
|
describe("successfully sets password", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/admin/users/${IdMap.getId("test-user")}/set-password`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
password: "987654321",
|
||||||
|
},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls UserService setPassword", () => {
|
||||||
|
expect(UserServiceMock.setPassword).toHaveBeenCalledTimes(1)
|
||||||
|
expect(UserServiceMock.setPassword).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("test-user"),
|
||||||
|
"987654321"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the user", () => {
|
||||||
|
expect(subject.body._id).toEqual(IdMap.getId("test-user"))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { UserServiceMock } from "../../../../../services/__mocks__/user"
|
||||||
|
|
||||||
|
describe("POST /admin/users/:id", () => {
|
||||||
|
describe("successful update", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/admin/users/${IdMap.getId("test-user")}`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
name: "Oliver Juhl",
|
||||||
|
},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls service method", () => {
|
||||||
|
expect(UserServiceMock.update).toHaveBeenCalledTimes(1)
|
||||||
|
expect(UserServiceMock.update).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("test-user"),
|
||||||
|
{
|
||||||
|
name: "Oliver Juhl",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Validator, MedusaError } from "medusa-core-utils"
|
||||||
|
import _ from "lodash"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
email: Validator.string()
|
||||||
|
.email()
|
||||||
|
.required(),
|
||||||
|
name: Validator.string().optional(),
|
||||||
|
password: Validator.string().required(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userService = req.scope.resolve("userService")
|
||||||
|
const data = _.pick(value, ["email", "name"])
|
||||||
|
|
||||||
|
const user = await userService.create(data, value.password)
|
||||||
|
|
||||||
|
res.status(200).json(user)
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export default async (req, res) => {
|
||||||
|
const { user_id } = req.params
|
||||||
|
|
||||||
|
const userService = req.scope.resolve("userService")
|
||||||
|
await userService.delete(user_id)
|
||||||
|
|
||||||
|
res.status(200).send({
|
||||||
|
id: user_id,
|
||||||
|
object: "user",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export default async (req, res) => {
|
||||||
|
const { user_id } = req.params
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userService = req.scope.resolve("userService")
|
||||||
|
|
||||||
|
const user = await userService.retrieve(user_id)
|
||||||
|
res.json(user)
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Router } from "express"
|
||||||
|
import middlewares from "../../../middlewares"
|
||||||
|
|
||||||
|
const route = Router()
|
||||||
|
|
||||||
|
export default app => {
|
||||||
|
app.use("/users", route)
|
||||||
|
|
||||||
|
route.get("/", middlewares.wrap(require("./list-users").default))
|
||||||
|
route.get("/:user_id", middlewares.wrap(require("./get-user").default))
|
||||||
|
|
||||||
|
route.post("/", middlewares.wrap(require("./create-user").default))
|
||||||
|
route.post("/:user_id", middlewares.wrap(require("./update-user").default))
|
||||||
|
route.post(
|
||||||
|
"/:user_id/set-password",
|
||||||
|
middlewares.wrap(require("./set-password").default)
|
||||||
|
)
|
||||||
|
route.post(
|
||||||
|
"/:user_id/reset-password-token",
|
||||||
|
middlewares.wrap(require("./reset-password-token").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
"/:user_id/reset-password",
|
||||||
|
middlewares.wrap(require("./reset-password").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.delete("/:user_id", middlewares.wrap(require("./delete-user").default))
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export default async (req, res) => {
|
||||||
|
try {
|
||||||
|
const userService = req.scope.resolve("userService")
|
||||||
|
const users = await userService.list({})
|
||||||
|
|
||||||
|
res.status(200).json(users)
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export default async (req, res) => {
|
||||||
|
const { user_id } = req.params
|
||||||
|
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,33 @@
|
|||||||
|
import { MedusaError, Validator } from "medusa-core-utils"
|
||||||
|
import jwt from "jsonwebtoken"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { user_id } = req.params
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
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 userService = req.scope.resolve("userService")
|
||||||
|
let user = await userService.retrieve(user_id)
|
||||||
|
|
||||||
|
const decodedToken = await jwt.verify(value.token, user.password_hash)
|
||||||
|
if (!decodedToken) {
|
||||||
|
res.status(401).send("Invalid or expired password reset token")
|
||||||
|
}
|
||||||
|
|
||||||
|
await userService.setPassword(user._id, value.password)
|
||||||
|
|
||||||
|
const updatedUser = await userService.retrieve(user._id)
|
||||||
|
|
||||||
|
res.status(200).json(updatedUser)
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { Validator, MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { user_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 userService = req.scope.resolve("userService")
|
||||||
|
await userService.setPassword(user_id, value.password)
|
||||||
|
|
||||||
|
const newUser = await userService.retrieve(user_id)
|
||||||
|
|
||||||
|
res.json(newUser)
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { MedusaError, Validator } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { user_id } = req.params
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
name: Validator.string().optional(),
|
||||||
|
api_token: Validator.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userService = req.scope.resolve("userService")
|
||||||
|
|
||||||
|
await userService.update(user_id, value)
|
||||||
|
const user = await userService.retrieve(user_id)
|
||||||
|
|
||||||
|
res.status(200).json(user)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ import { Router } from "express"
|
|||||||
import productRoutes from "./products"
|
import productRoutes from "./products"
|
||||||
import cartRoutes from "./carts"
|
import cartRoutes from "./carts"
|
||||||
import shippingOptionRoutes from "./shipping-options"
|
import shippingOptionRoutes from "./shipping-options"
|
||||||
import middlewares from "../../middlewares"
|
|
||||||
|
|
||||||
const route = Router()
|
const route = Router()
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import { Router } from "express"
|
|
||||||
import middlewares from "../../middlewares"
|
|
||||||
|
|
||||||
const route = Router()
|
|
||||||
|
|
||||||
export default app => {
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
@@ -4,12 +4,12 @@ export const users = {
|
|||||||
testUser: {
|
testUser: {
|
||||||
_id: IdMap.getId("test-user"),
|
_id: IdMap.getId("test-user"),
|
||||||
email: "oliver@medusa.test",
|
email: "oliver@medusa.test",
|
||||||
passwordHash: "123456789",
|
password_hash: "123456789",
|
||||||
},
|
},
|
||||||
permissionUser: {
|
permissionUser: {
|
||||||
_id: IdMap.getId("permissions-user"),
|
_id: IdMap.getId("permissions-user"),
|
||||||
email: "oliver@medusa.com",
|
email: "oliver@medusa.com",
|
||||||
passwordHash: "123456789",
|
password_hash: "123456789",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ class UserModel extends BaseModel {
|
|||||||
static schema = {
|
static schema = {
|
||||||
email: { type: String, required: true },
|
email: { type: String, required: true },
|
||||||
name: { type: String },
|
name: { type: String },
|
||||||
password_hash: { type: String },
|
password_hash: { type: String, required: true },
|
||||||
|
api_token: { type: String },
|
||||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,64 @@
|
|||||||
import bcrypt from "bcrypt"
|
import bcrypt from "bcrypt"
|
||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import _ from "lodash"
|
||||||
|
|
||||||
export const users = {
|
export const users = {
|
||||||
user1: {
|
testUser: {
|
||||||
|
_id: IdMap.getId("test-user"),
|
||||||
email: "oliver@test.dk",
|
email: "oliver@test.dk",
|
||||||
password_hash: "123456789",
|
password_hash: "hashed123456789",
|
||||||
api_token: "123456789",
|
},
|
||||||
|
jwtUser: {
|
||||||
|
_id: "test-user-id",
|
||||||
|
email: "oliver@test.dk",
|
||||||
|
password_hash: "123456789hash",
|
||||||
|
},
|
||||||
|
deleteUser: {
|
||||||
|
_id: IdMap.getId("delete-user"),
|
||||||
|
email: "oliver@deletetest.dk",
|
||||||
|
password_hash: "hashed123456789",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserServiceMock = {
|
export const UserServiceMock = {
|
||||||
|
create: jest.fn().mockImplementation(data => {
|
||||||
|
if (data.email === "oliver@test.dk") {
|
||||||
|
return Promise.resolve(users.testUser)
|
||||||
|
}
|
||||||
|
return Promise.resolve(undefined)
|
||||||
|
}),
|
||||||
|
update: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
|
list: jest.fn().mockReturnValue(Promise.resolve([])),
|
||||||
|
delete: jest.fn().mockImplementation(data => {
|
||||||
|
if (data === IdMap.getId("delete-user")) {
|
||||||
|
return Promise.resolve({
|
||||||
|
id: IdMap.getId("delete-user"),
|
||||||
|
object: "user",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Promise.resolve(undefined)
|
||||||
|
}),
|
||||||
|
retrieve: jest.fn().mockImplementation(userId => {
|
||||||
|
if (userId === IdMap.getId("test-user")) {
|
||||||
|
return Promise.resolve(users.testUser)
|
||||||
|
}
|
||||||
|
// used for jwt token tests
|
||||||
|
if (userId === "test-user-id") {
|
||||||
|
return Promise.resolve(users.jwtUser)
|
||||||
|
}
|
||||||
|
return Promise.resolve(undefined)
|
||||||
|
}),
|
||||||
|
setPassword: jest.fn().mockImplementation(userId => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
|
decorate: jest.fn().mockImplementation((user, fields) => {
|
||||||
|
user.decorated = true
|
||||||
|
return user
|
||||||
|
}),
|
||||||
|
generateResetPasswordToken: jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(Promise.resolve("JSONWEBTOKEN")),
|
||||||
retrieveByApiToken: jest.fn().mockImplementation(token => {
|
retrieveByApiToken: jest.fn().mockImplementation(token => {
|
||||||
if (token === "123456789") {
|
if (token === "123456789") {
|
||||||
return Promise.resolve(users.user1)
|
return Promise.resolve(users.user1)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import bcrypt from "bcrypt"
|
|
||||||
import AuthService from "../auth"
|
import AuthService from "../auth"
|
||||||
import { users, UserServiceMock } from "../__mocks__/user"
|
import { users, UserServiceMock } from "../__mocks__/user"
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ describe("UserService", () => {
|
|||||||
result = await userService.retrieve(IdMap.getId("test-user"))
|
result = await userService.retrieve(IdMap.getId("test-user"))
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls cart model functions", () => {
|
it("calls user model functions", () => {
|
||||||
expect(UserModelMock.findOne).toHaveBeenCalledTimes(1)
|
expect(UserModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||||
expect(UserModelMock.findOne).toHaveBeenCalledWith({
|
expect(UserModelMock.findOne).toHaveBeenCalledWith({
|
||||||
_id: IdMap.getId("test-user"),
|
_id: IdMap.getId("test-user"),
|
||||||
@@ -28,6 +28,60 @@ describe("UserService", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("create", () => {
|
||||||
|
let result
|
||||||
|
|
||||||
|
const userService = new UserService({
|
||||||
|
userModel: UserModelMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls user model functions", async () => {
|
||||||
|
result = await userService.create(
|
||||||
|
{
|
||||||
|
email: "oliver@test.dk",
|
||||||
|
name: "Oliver",
|
||||||
|
password_hash: "hashedpassword",
|
||||||
|
},
|
||||||
|
"password"
|
||||||
|
)
|
||||||
|
expect(UserModelMock.create).toHaveBeenCalledTimes(1)
|
||||||
|
expect(UserModelMock.create).toHaveBeenCalledWith({
|
||||||
|
email: "oliver@test.dk",
|
||||||
|
name: "Oliver",
|
||||||
|
password_hash: expect.stringMatching(
|
||||||
|
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("update", () => {
|
||||||
|
const userService = new UserService({
|
||||||
|
userModel: UserModelMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls updateOne with correct params", async () => {
|
||||||
|
await userService.update(IdMap.getId("test-user"), {
|
||||||
|
name: "new name",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(UserModelMock.updateOne).toBeCalledTimes(1)
|
||||||
|
expect(UserModelMock.updateOne).toBeCalledWith(
|
||||||
|
{ _id: IdMap.getId("test-user") },
|
||||||
|
{ $set: { name: "new name" } },
|
||||||
|
{ runValidators: true }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("setMetadata", () => {
|
describe("setMetadata", () => {
|
||||||
const userService = new UserService({
|
const userService = new UserService({
|
||||||
userModel: UserModelMock,
|
userModel: UserModelMock,
|
||||||
|
|||||||
@@ -135,14 +135,56 @@ class UserService extends BaseService {
|
|||||||
* @param {object} user - the user to create
|
* @param {object} user - the user to create
|
||||||
* @return {Promise} the result of create
|
* @return {Promise} the result of create
|
||||||
*/
|
*/
|
||||||
create(user) {
|
async create(user, password) {
|
||||||
const validatedEmail = this.validateEmail_(user.email)
|
const validatedEmail = this.validateEmail_(user.email)
|
||||||
|
const hashedPassword = await bcrypt.hash(password, 10)
|
||||||
user.email = validatedEmail
|
user.email = validatedEmail
|
||||||
|
user.password_hash = hashedPassword
|
||||||
return this.userModel_.create(user).catch(err => {
|
return this.userModel_.create(user).catch(err => {
|
||||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a user.
|
||||||
|
* @param {object} user - the user to create
|
||||||
|
* @return {Promise} the result of create
|
||||||
|
*/
|
||||||
|
async update(userId, update) {
|
||||||
|
const validatedId = this.validateId_(userId)
|
||||||
|
|
||||||
|
if (update.password_hash) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Use dedicated methods, `setPassword`, `generateResetPasswordToken` for password operations"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update.email) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"You are not allowed to update email"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update.metadata) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Use setMetadata to update metadata fields"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.userModel_
|
||||||
|
.updateOne(
|
||||||
|
{ _id: validatedId },
|
||||||
|
{ $set: update },
|
||||||
|
{ runValidators: true }
|
||||||
|
)
|
||||||
|
.catch(err => {
|
||||||
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a user from a given user id.
|
* Deletes a user from a given user id.
|
||||||
* @param {string} userId - the id of the user to delete. Must be
|
* @param {string} userId - the id of the user to delete. Must be
|
||||||
@@ -177,7 +219,7 @@ class UserService extends BaseService {
|
|||||||
const hashedPassword = await bcrypt.hash(password, 10)
|
const hashedPassword = await bcrypt.hash(password, 10)
|
||||||
if (!hashedPassword) {
|
if (!hashedPassword) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.INVALID_DATA,
|
MedusaError.Types.DB_ERROR,
|
||||||
`An error occured while hashing password`
|
`An error occured while hashing password`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -199,7 +241,7 @@ class UserService extends BaseService {
|
|||||||
*/
|
*/
|
||||||
async generateResetPasswordToken(userId) {
|
async generateResetPasswordToken(userId) {
|
||||||
const user = await this.retrieve(userId)
|
const user = await this.retrieve(userId)
|
||||||
const secret = user.passwordHash
|
const secret = user.password_hash
|
||||||
const expiry = Math.floor(Date.now() / 1000) + 60 * 15
|
const expiry = Math.floor(Date.now() / 1000) + 60 * 15
|
||||||
const payload = { userId: user._id, exp: expiry }
|
const payload = { userId: user._id, exp: expiry }
|
||||||
const token = jwt.sign(payload, secret)
|
const token = jwt.sign(payload, secret)
|
||||||
|
|||||||
Reference in new issue
Block a user