chore: merge release

This commit is contained in:
Sebastian Rindom
2021-11-20 15:59:31 +01:00
565 changed files with 38806 additions and 16346 deletions
+18 -1
View File
@@ -1,7 +1,7 @@
import { Router } from "express"
import errorHandler from "./middlewares/error-handler"
import admin from "./routes/admin"
import store from "./routes/store"
import errorHandler from "./middlewares/error-handler"
// guaranteed to get dependencies
export default (container, config) => {
@@ -14,3 +14,20 @@ export default (container, config) => {
return app
}
export * from "./routes/admin/notifications"
export * from "./routes/admin/store"
export * from "./routes/admin/variants"
export * from "./routes/store/auth"
export * from "./routes/store/carts"
export * from "./routes/store/collections"
export * from "./routes/store/customers"
export * from "./routes/store/gift-cards"
export * from "./routes/store/orders"
export * from "./routes/store/products"
export * from "./routes/store/regions"
export * from "./routes/store/return-reasons"
export * from "./routes/store/returns"
export * from "./routes/store/shipping-options"
export * from "./routes/store/swaps"
export * from "./routes/store/variants"
@@ -1 +1,3 @@
export default fn => (...args) => fn(...args).catch(args[2])
export default (fn) =>
(...args) =>
fn(...args).catch(args[2])
@@ -1,25 +0,0 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const schema = Validator.object().keys({
application_name: Validator.string().required(),
state: Validator.string().required(),
code: Validator.string().required(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const oauthService = req.scope.resolve("oauthService")
const data = await oauthService.generateToken(
value.application_name,
value.code,
value.state
)
res.status(200).json({ apps: data })
} catch (err) {
throw err
}
}
@@ -0,0 +1,63 @@
import { IsNotEmpty, IsString } from "class-validator"
import { OauthService } from "../../../../services"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /apps
* operationId: "PostApps"
* summary: "Generates a token for an application."
* description: "Generates a token for an application."
* x-authenticated: true
* requestBody:
* content:
* application/json:
* schema:
* required:
* - application_name
* - state
* - code
* properties:
* application_name:
* type: string
* description: Name of the application for the token to be generated for.
* state:
* type: string
* description: State of the application.
* code:
* type: string
* description: The code for the generated token.
* tags:
* - Apps
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* properties:
* apps:
* $ref: "#/components/schemas/OAuth"
*/
export default async (req, res) => {
const validated = await validator(AdminPostAppsReq, req.body)
const oauthService: OauthService = req.scope.resolve("oauthService")
const data = await oauthService.generateToken(
validated.application_name,
validated.code,
validated.state
)
res.status(200).json({ apps: data })
}
export class AdminPostAppsReq {
@IsString()
@IsNotEmpty()
application_name: string
@IsString()
@IsNotEmpty()
state: string
@IsString()
@IsNotEmpty()
code: string
}
@@ -1,9 +1,10 @@
import { Router } from "express"
import { Oauth } from "../../../.."
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
export default (app) => {
app.use("/apps", route)
route.get("/", middlewares.wrap(require("./list").default))
@@ -14,3 +15,11 @@ export default app => {
return app
}
export type AdminAppsRes = {
apps: Oauth
}
export type AdminAppsListRes = {
apps: Oauth[]
}
@@ -1,12 +0,0 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
try {
const oauthService = req.scope.resolve("oauthService")
const data = await oauthService.list({})
res.status(200).json({ apps: data })
} catch (err) {
throw err
}
}
@@ -0,0 +1,26 @@
import { OauthService } from "../../../../services"
/**
* @oas [get] /apps
* operationId: "GetApps"
* summary: "List applications"
* description: "Retrieve a list of applications."
* x-authenticated: true
* tags:
* - Apps
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* properties:
* collection:
* $ref: "#/components/schemas/OAuth"
*/
export default async (req, res) => {
const oauthService: OauthService = req.scope.resolve("oauthService")
const data = await oauthService.list({})
res.status(200).json({ apps: data })
}
@@ -1,53 +0,0 @@
import _ from "lodash"
import jwt from "jsonwebtoken"
import { Validator } from "medusa-core-utils"
import config from "../../../../config"
/**
* @oas [post] /auth
* operationId: "PostAuth"
* summary: "Authenticate a User"
* description: "Logs a User in and authorizes them to manage Store settings."
* parameters:
* - (body) email=* {string} The User's email.
* - (body) password=* {string} The User's password.
* tags:
* - Auth
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* properties:
* user:
* $ref: "#/components/schemas/user"
*/
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.authenticate(value.email, value.password)
if (!result.success) {
res.sendStatus(401)
return
}
// Add JWT to cookie
req.session.jwt = jwt.sign({ userId: result.user.id }, config.jwtSecret, {
expiresIn: "24h",
})
const cleanRes = _.omit(result.user, ["password_hash"])
res.json({ user: cleanRes })
}
@@ -0,0 +1,67 @@
import _ from "lodash"
import jwt from "jsonwebtoken"
import config from "../../../../config"
import { validator } from "../../../../utils/validator"
import { IsEmail, IsNotEmpty, IsString } from "class-validator"
import AuthService from "../../../../services/auth"
import { MedusaError } from "medusa-core-utils"
/**
* @oas [post] /auth
* operationId: "PostAuth"
* summary: "Authenticate a User"
* x-authenticated: false
* description: "Logs a User in and authorizes them to manage Store settings."
* parameters:
* - (body) email=* {string} The User's email.
* - (body) password=* {string} The User's password.
* tags:
* - Auth
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* properties:
* user:
* $ref: "#/components/schemas/user"
*/
export default async (req, res) => {
if (!config.jwtSecret) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
"Please configure jwtSecret in your environment"
)
}
const validated = await validator(AdminPostAuthReq, req.body)
const authService: AuthService = req.scope.resolve("authService")
const result = await authService.authenticate(
validated.email,
validated.password
)
if (result.success && result.user) {
// Add JWT to cookie
req.session.jwt = jwt.sign({ userId: result.user.id }, config.jwtSecret, {
expiresIn: "24h",
})
const cleanRes = _.omit(result.user, ["password_hash"])
res.json({ user: cleanRes })
} else {
res.sendStatus(401)
}
}
export class AdminPostAuthReq {
@IsEmail()
@IsNotEmpty()
email: string
@IsString()
@IsNotEmpty()
password: string
}
@@ -1,9 +1,8 @@
import _ from "lodash"
/**
* @oas [get] /auth
* operationId: "DeleteAuth"
* summary: "Delete Session"
* x-authenticated: true
* description: "Deletes the current session for the logged in user."
* tags:
* - Auth
@@ -1,9 +1,11 @@
import _ from "lodash"
import UserService from "../../../../services/user"
/**
* @oas [get] /auth
* operationId: "GetAuth"
* summary: "Get Session"
* x-authenticated: true
* description: "Gets the currently logged in User."
* tags:
* - Auth
@@ -18,9 +20,13 @@ import _ from "lodash"
* $ref: "#/components/schemas/user"
*/
export default async (req, res) => {
const userService = req.scope.resolve("userService")
const user = await userService.retrieve(req.user.userId)
try {
const userService: UserService = req.scope.resolve("userService")
const user = await userService.retrieve(req.user.userId)
const cleanRes = _.omit(user, ["password_hash"])
res.status(200).json({ user: cleanRes })
const cleanRes = _.omit(user, ["password_hash"])
res.status(200).json({ user: cleanRes })
} catch (err) {
res.sendStatus(400)
}
}
@@ -1,9 +1,10 @@
import { Router } from "express"
import { User } from "../../../.."
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
export default (app) => {
app.use("/auth", route)
route.get(
@@ -21,3 +22,11 @@ export default app => {
return app
}
export type AdminAuthRes = {
user: Omit<User, "password_hash">
}
export * from "./create-session"
export * from "./delete-session"
export * from "./get-session"
@@ -59,7 +59,9 @@ describe("POST /admin/collections", () => {
it("returns error details", () => {
expect(subject.body.type).toEqual("invalid_data")
expect(subject.body.message[0].message).toEqual(`"title" is required`)
expect(subject.body.message).toEqual(
"title should not be empty, title must be a string"
)
})
})
})
@@ -22,7 +22,7 @@ describe("GET /admin/collections", () => {
})
it("calls product collection service list", () => {
expect(ProductCollectionServiceMock.list).toHaveBeenCalledTimes(1)
expect(ProductCollectionServiceMock.listAndCount).toHaveBeenCalledTimes(1)
})
})
})
@@ -1,10 +1,12 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { IsNotEmpty, IsObject, IsOptional, IsString } from "class-validator"
import ProductCollectionService from "../../../../services/product-collection"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /collections
* operationId: "PostCollections"
* summary: "Create a Product Collection"
* description: "Creates a Product Collection."
* x-authenticated: true
* requestBody:
* content:
* application/json:
@@ -34,29 +36,28 @@ import { MedusaError, Validator } from "medusa-core-utils"
* $ref: "#/components/schemas/product_collection"
*/
export default async (req, res) => {
const schema = Validator.object().keys({
title: Validator.string().required(),
handle: Validator.string()
.optional()
.allow(""),
metadata: Validator.object().optional(),
})
const validated = await validator(AdminPostCollectionsReq, req.body)
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const productCollectionService: ProductCollectionService = req.scope.resolve(
"productCollectionService"
)
try {
const productCollectionService = req.scope.resolve(
"productCollectionService"
)
const created = await productCollectionService.create(validated)
const collection = await productCollectionService.retrieve(created.id)
const created = await productCollectionService.create(value)
const collection = await productCollectionService.retrieve(created.id)
res.status(200).json({ collection })
}
res.status(200).json({ collection })
} catch (err) {
throw err
}
export class AdminPostCollectionsReq {
@IsString()
@IsNotEmpty()
title: string
@IsString()
@IsOptional()
handle?: string
@IsObject()
@IsOptional()
metadata?: object
}
@@ -1,8 +1,11 @@
import ProductCollectionService from "../../../../services/product-collection"
/**
* @oas [delete] /collections/{id}
* operationId: "DeleteCollectionsCollection"
* summary: "Delete a Product Collection"
* description: "Deletes a Product Collection."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Collection.
* tags:
@@ -26,18 +29,14 @@
export default async (req, res) => {
const { id } = req.params
try {
const productCollectionService = req.scope.resolve(
"productCollectionService"
)
await productCollectionService.delete(id)
const productCollectionService: ProductCollectionService = req.scope.resolve(
"productCollectionService"
)
await productCollectionService.delete(id)
res.json({
id,
object: "product-collection",
deleted: true,
})
} catch (err) {
throw err
}
res.json({
id,
object: "product-collection",
deleted: true,
})
}
@@ -0,0 +1,31 @@
import ProductCollectionService from "../../../../services/product-collection"
/**
* @oas [get] /collections/{id}
* operationId: "GetCollectionsCollection"
* summary: "Retrieve a Product Collection"
* description: "Retrieves a Product Collection."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Product Collection
* tags:
* - Collection
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* properties:
* collection:
* $ref: "#/components/schemas/product_collection"
*/
export default async (req, res) => {
const { id } = req.params
const productCollectionService: ProductCollectionService = req.scope.resolve(
"productCollectionService"
)
const collection = await productCollectionService.retrieve(id)
res.status(200).json({ collection })
}
@@ -1,21 +0,0 @@
import { Router } from "express"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
app.use("/collections", route)
route.post("/", middlewares.wrap(require("./create-collection").default))
route.post("/:id", middlewares.wrap(require("./update-collection").default))
route.delete("/:id", middlewares.wrap(require("./delete-collection").default))
route.get("/:id", middlewares.wrap(require("./get-collection").default))
route.get("/", middlewares.wrap(require("./list-collections").default))
return app
}
export const defaultFields = ["id", "title", "handle"]
export const defaultRelations = ["products"]
@@ -0,0 +1,40 @@
import { Router } from "express"
import { ProductCollection } from "../../../.."
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
import middlewares from "../../../middlewares"
import "reflect-metadata"
const route = Router()
export default (app) => {
app.use("/collections", route)
route.post("/", middlewares.wrap(require("./create-collection").default))
route.post("/:id", middlewares.wrap(require("./update-collection").default))
route.delete("/:id", middlewares.wrap(require("./delete-collection").default))
route.get("/:id", middlewares.wrap(require("./get-collection").default))
route.get("/", middlewares.wrap(require("./list-collections").default))
return app
}
export const defaultAdminCollectionsFields = ["id", "title", "handle"]
export const defaultAdminCollectionsRelations = ["products"]
export type AdminCollectionsListRes = PaginatedResponse & {
collections: ProductCollection[]
}
export type AdminCollectionsDeleteRes = DeleteResponse
export type AdminCollectionsRes = {
collection: ProductCollection
}
export * from "./create-collection"
export * from "./delete-collection"
export * from "./get-collection"
export * from "./list-collections"
export * from "./update-collection"
@@ -1,47 +0,0 @@
import { defaultFields, defaultRelations } from "."
/**
* @oas [get] /collections
* operationId: "GetCollections"
* summary: "List Product Collections"
* description: "Retrieve a list of Product Collection."
* tags:
* - Collection
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* properties:
* collection:
* $ref: "#/components/schemas/product_collection"
*/
export default async (req, res) => {
try {
const selector = {}
const limit = parseInt(req.query.limit) || 10
const offset = parseInt(req.query.offset) || 0
const productCollectionService = req.scope.resolve(
"productCollectionService"
)
const listConfig = {
select: defaultFields,
relations: defaultRelations,
skip: offset,
take: limit,
}
const collections = await productCollectionService.list(
selector,
listConfig
)
res.status(200).json({ collections })
} catch (err) {
throw err
}
}
@@ -0,0 +1,67 @@
import { Type } from "class-transformer"
import { IsNumber, IsOptional } from "class-validator"
import {
defaultAdminCollectionsFields,
defaultAdminCollectionsRelations,
} from "."
import ProductCollectionService from "../../../../services/product-collection"
import { validator } from "../../../../utils/validator"
/**
* @oas [get] /collections
* operationId: "GetCollections"
* summary: "List Product Collections"
* description: "Retrieve a list of Product Collection."
* x-authenticated: true
* parameters:
* - (path) limit {string} The number of collections to return.
* - (path) offset {string} The offset of collections to return.
* tags:
* - Collection
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* properties:
* collection:
* $ref: "#/components/schemas/product_collection"
*/
export default async (req, res) => {
const validated = await validator(AdminGetCollectionsParams, req.query)
const productCollectionService: ProductCollectionService = req.scope.resolve(
"productCollectionService"
)
const listConfig = {
select: defaultAdminCollectionsFields,
relations: defaultAdminCollectionsRelations,
skip: validated.offset,
take: validated.limit,
}
const [collections, count] = await productCollectionService.listAndCount(
{},
listConfig
)
res.status(200).json({
collections,
count,
offset: validated.offset,
limit: validated.limit,
})
}
export class AdminGetCollectionsParams {
@IsNumber()
@IsOptional()
@Type(() => Number)
limit = 10
@IsNumber()
@IsOptional()
@Type(() => Number)
offset = 0
}
@@ -1,10 +1,12 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { IsObject, IsOptional, IsString } from "class-validator"
import ProductCollectionService from "../../../../services/product-collection"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /collections/{id}
* operationId: "PostCollectionsCollection"
* summary: "Update a Product Collection"
* description: "Updates a Product Collection."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Collection.
* requestBody:
@@ -36,27 +38,27 @@ import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
title: Validator.string().optional(),
handle: Validator.string().optional(),
metadata: Validator.object().optional(),
})
const validated = await validator(AdminPostCollectionsCollectionReq, req.body)
const productCollectionService: ProductCollectionService = req.scope.resolve(
"productCollectionService"
)
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const updated = await productCollectionService.update(id, validated)
const collection = await productCollectionService.retrieve(updated.id)
try {
const productCollectionService = req.scope.resolve(
"productCollectionService"
)
res.status(200).json({ collection })
}
const updated = await productCollectionService.update(id, value)
const collection = await productCollectionService.retrieve(updated.id)
export class AdminPostCollectionsCollectionReq {
@IsString()
@IsOptional()
title?: string
res.status(200).json({ collection })
} catch (err) {
throw err
}
@IsString()
@IsOptional()
handle?: string
@IsObject()
@IsOptional()
metadata?: object
}
@@ -1,47 +0,0 @@
import { Validator, MedusaError } from "medusa-core-utils"
/**
* @oas [post] /customers
* operationId: "PostCustomers"
* summary: "Create a Customer"
* description: "Creates a Customer."
* parameters:
* - (body) email=* {string} The Customer's email address.
* - (body) first_name=* {string} The Customer's first name.
* - (body) last_name=* {string} The Customer's last name.
* - (body) phone {string} The Customer's phone number.
* tags:
* - Customer
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* customer:
* $ref: "#/components/schemas/customer"
*/
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(),
phone: Validator.string().optional(),
})
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)
res.status(201).json({ customer })
} catch (err) {
throw err
}
}
@@ -0,0 +1,57 @@
import { IsEmail, IsObject, IsOptional, IsString } from "class-validator"
import { CustomerService } from "../../../../services"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /customers
* operationId: "PostCustomers"
* summary: "Create a Customer"
* description: "Creates a Customer."
* x-authenticated: true
* parameters:
* - (body) email=* {string} The Customer's email address.
* - (body) first_name=* {string} The Customer's first name.
* - (body) last_name=* {string} The Customer's last name.
* - (body) phone {string} The Customer's phone number.
* - (body) metadata {object} Metadata for the customer.
* tags:
* - Customer
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* customer:
* $ref: "#/components/schemas/customer"
*/
export default async (req, res) => {
const validated = await validator(AdminPostCustomersReq, req.bodyn)
const customerService: CustomerService = req.scope.resolve("customerService")
const customer = await customerService.create(validated)
res.status(201).json({ customer })
}
export class AdminPostCustomersReq {
@IsEmail()
email: string
@IsString()
first_name: string
@IsString()
last_name: string
@IsString()
password: string
@IsString()
@IsOptional()
phone?: string
@IsObject()
@IsOptional()
metadata?: object
}
@@ -1,8 +1,11 @@
import CustomerService from "../../../../services/customer"
/**
* @oas [get] /customers/{id}
* operationId: "GetCustomersCustomer"
* summary: "Retrieve a Customer"
* description: "Retrieves a Customer."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Customer.
* tags:
@@ -19,14 +22,10 @@
*/
export default async (req, res) => {
const { id } = req.params
try {
const customerService = req.scope.resolve("customerService")
const customer = await customerService.retrieve(id, {
relations: ["orders", "shipping_addresses"],
})
const customerService: CustomerService = req.scope.resolve("customerService")
const customer = await customerService.retrieve(id, {
relations: ["orders", "shipping_addresses"],
})
res.json({ customer })
} catch (err) {
throw err
}
res.json({ customer })
}
@@ -1,15 +0,0 @@
import { Router } from "express"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
app.use("/customers", route)
route.get("/", middlewares.wrap(require("./list-customers").default))
route.get("/:id", middlewares.wrap(require("./get-customer").default))
route.post("/", middlewares.wrap(require("./create-customer").default))
route.post("/:id", middlewares.wrap(require("./update-customer").default))
return app
}
@@ -0,0 +1,32 @@
import { Router } from "express"
import { Customer } from "../../../.."
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
import middlewares from "../../../middlewares"
const route = Router()
export default (app) => {
app.use("/customers", route)
route.get("/", middlewares.wrap(require("./list-customers").default))
route.get("/:id", middlewares.wrap(require("./get-customer").default))
route.post("/", middlewares.wrap(require("./create-customer").default))
route.post("/:id", middlewares.wrap(require("./update-customer").default))
return app
}
export type AdminCustomersRes = {
customer: Customer
}
export type AdminCustomersDeleteRes = DeleteResponse
export type AdminCustomersListRes = PaginatedResponse & {
customers: Customer[]
}
export * from "./create-customer"
export * from "./get-customer"
export * from "./list-customers"
export * from "./update-customer"
@@ -1,51 +0,0 @@
/**
* @oas [get] /customers
* operationId: "GetCustomers"
* summary: "List Customers"
* description: "Retrieves a list of Customers."
* tags:
* - Customer
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* customer:
* $ref: "#/components/schemas/customer"
*/
export default async (req, res) => {
try {
const customerService = req.scope.resolve("customerService")
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: expandFields.length ? expandFields : [],
skip: offset,
take: limit,
}
const [customers, count] = await customerService.listAndCount(
selector,
listConfig
)
res.json({ customers, count, offset, limit })
} catch (error) {
throw error
}
}
@@ -0,0 +1,75 @@
import { Type } from "class-transformer"
import { IsNumber, IsOptional, IsString } from "class-validator"
import { Customer } from "../../../.."
import CustomerService from "../../../../services/customer"
import { FindConfig } from "../../../../types/common"
import { AdminListCustomerSelector } from "../../../../types/customers"
import { validator } from "../../../../utils/validator"
/**
* @oas [get] /customers
* operationId: "GetCustomers"
* 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 validated = await validator(AdminGetCustomersParams, req.query)
const customerService: CustomerService = req.scope.resolve("customerService")
const selector: AdminListCustomerSelector = {}
if (validated.q) {
selector.q = validated.q
}
let expandFields: string[] = []
if (validated.expand) {
expandFields = validated.expand.split(",")
}
const listConfig: FindConfig<Customer> = {
relations: expandFields,
skip: validated.offset,
take: validated.limit,
}
const [customers, count] = await customerService.listAndCount(
selector,
listConfig
)
res.json({
customers,
count,
offset: validated.offset,
limit: validated.limit,
})
}
export class AdminGetCustomersParams extends AdminListCustomerSelector {
@IsNumber()
@IsOptional()
@Type(() => Number)
limit = 50
@IsNumber()
@IsOptional()
@Type(() => Number)
offset = 0
@IsString()
@IsOptional()
expand?: string
}
@@ -1,76 +0,0 @@
import { Validator, MedusaError } from "medusa-core-utils"
/**
* @oas [post] /customers/{id}
* operationId: "PostCustomersCustomer"
* summary: "Update a Customer"
* description: "Updates a Customer."
* parameters:
* - (path) id=* {string} The id of the Customer.
* requestBody:
* content:
* application/json:
* schema:
* properties:
* email:
* type: string
* description: The Customer's email. Only providable if user not registered.
* first_name:
* type: string
* description: The Customer's first name.
* last_name:
* type: string
* description: The Customer's last name.
* phone:
* description: The Customer's phone number.
* type: object
* 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
const schema = Validator.object().keys({
email: Validator.string().optional(),
first_name: Validator.string().optional(),
last_name: Validator.string().optional(),
password: Validator.string().optional(),
phone: Validator.string().optional(),
})
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")
let customer = await customerService.retrieve(id)
if (value.email && customer.has_account) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Email cannot be changed when the user has registered their account"
)
}
await customerService.update(id, value)
customer = await customerService.retrieve(id, {
relations: ["orders"],
})
res.status(200).json({ customer })
} catch (err) {
throw err
}
}
@@ -0,0 +1,97 @@
import { IsEmail, IsObject, IsOptional, IsString } from "class-validator"
import { MedusaError } from "medusa-core-utils"
import CustomerService from "../../../../services/customer"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /customers/{id}
* operationId: "PostCustomersCustomer"
* summary: "Update a Customer"
* description: "Updates a Customer."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Customer.
* requestBody:
* content:
* application/json:
* schema:
* properties:
* email:
* type: string
* description: The Customer's email. Only providable if user not registered.
* first_name:
* type: string
* description: The Customer's first name.
* last_name:
* type: string
* description: The Customer's last name.
* phone:
* type: string
* description: The Customer's phone number.
* password:
* type: string
* description: The Customer's password.
* metadata:
* type: object
* description: Metadata for the customer.
* 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
const validated = await validator(AdminPostCustomersCustomerReq, req.body)
const customerService: CustomerService = req.scope.resolve("customerService")
let customer = await customerService.retrieve(id)
if (validated.email && customer.has_account) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Email cannot be changed when the user has registered their account"
)
}
await customerService.update(id, validated)
customer = await customerService.retrieve(id, {
relations: ["orders"],
})
res.status(200).json({ customer })
}
export class AdminPostCustomersCustomerReq {
@IsEmail()
@IsOptional()
email?: string
@IsString()
@IsOptional()
first_name?: string
@IsString()
@IsOptional()
last_name?: string
@IsString()
@IsOptional()
password?: string
@IsString()
@IsOptional()
phone?: string
@IsObject()
@IsOptional()
metadata?: object
}
@@ -80,7 +80,7 @@ describe("POST /admin/discounts", () => {
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
expect(subject.body.message).toEqual(
`"valid_duration" must be a valid ISO 8601 duration`
)
})
@@ -160,7 +160,9 @@ describe("POST /admin/discounts", () => {
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(`"rule.type" is required`)
expect(subject.body.message).toEqual(
`type should not be empty, type must be a string`
)
})
})
@@ -193,8 +195,8 @@ describe("POST /admin/discounts", () => {
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"ends_at" must be greater than "ref:starts_at"`
expect(subject.body.message).toEqual(
`"ends_at" must be greater than "starts_at"`
)
})
})
@@ -231,8 +233,8 @@ describe("POST /admin/discounts", () => {
it("returns error", () => {
expect(DiscountServiceMock.create).toHaveBeenCalledWith({
code: "TEST",
is_dynamic: true,
is_disabled: false,
is_dynamic: true,
is_disabled: false,
rule: {
description: "Test",
type: "fixed",
@@ -1,5 +1,5 @@
import { IdMap } from "medusa-test-utils"
import { defaultFields, defaultRelations } from ".."
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from ".."
import { request } from "../../../../../helpers/test-request"
import { DiscountServiceMock } from "../../../../../services/__mocks__/discount"
@@ -27,8 +27,8 @@ describe("GET /admin/discounts", () => {
expect(DiscountServiceMock.listAndCount).toHaveBeenCalledWith(
{},
{
select: defaultFields,
relations: defaultRelations,
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
skip: 0,
take: 20,
order: { created_at: "DESC" },
@@ -64,8 +64,8 @@ describe("GET /admin/discounts", () => {
expect(DiscountServiceMock.listAndCount).toHaveBeenCalledWith(
{ q: "OLI", is_dynamic: false, is_disabled: false },
{
select: defaultFields,
relations: defaultRelations,
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
skip: 20,
take: 40,
order: { created_at: "DESC" },
@@ -97,8 +97,8 @@ describe("GET /admin/discounts", () => {
expect(DiscountServiceMock.listAndCount).toHaveBeenCalledWith(
{ is_dynamic: true },
{
select: defaultFields,
relations: defaultRelations,
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
skip: 0,
take: 20,
order: { created_at: "DESC" },
@@ -130,8 +130,8 @@ describe("GET /admin/discounts", () => {
expect(DiscountServiceMock.listAndCount).toHaveBeenCalledWith(
{ is_disabled: true },
{
select: defaultFields,
relations: defaultRelations,
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
skip: 0,
take: 20,
order: { created_at: "DESC" },
@@ -46,7 +46,6 @@ describe("POST /admin/discounts", () => {
value: 10,
allocation: "total",
},
is_dynamic: false,
}
)
})
@@ -87,7 +86,7 @@ describe("POST /admin/discounts", () => {
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
expect(subject.body.message).toEqual(
`"valid_duration" must be a valid ISO 8601 duration`
)
})
@@ -181,8 +180,8 @@ describe("POST /admin/discounts", () => {
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"ends_at" must be greater than "ref:starts_at"`
expect(subject.body.message).toEqual(
`"ends_at" must be greater than "starts_at"`
)
})
})
@@ -1,10 +1,12 @@
import { defaultFields, defaultRelations } from "./"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import { Discount } from "../../../.."
import DiscountService from "../../../../services/discount"
/**
* @oas [post] /discounts/{id}/regions/{region_id}
* operationId: "PostDiscountsDiscountRegionsRegion"
* summary: "Adds Region availability"
* description: "Adds a Region to the list of Regions that a Discount can be used in."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount.
* - (path) region_id=* {string} The id of the Region.
@@ -22,18 +24,14 @@ import { defaultFields, defaultRelations } from "./"
*/
export default async (req, res) => {
const { discount_id, region_id } = req.params
try {
const discountService = req.scope.resolve("discountService")
await discountService.addRegion(discount_id, region_id)
const discountService: DiscountService = req.scope.resolve("discountService")
await discountService.addRegion(discount_id, region_id)
const discount = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
})
const discount: Discount = await discountService.retrieve(discount_id, {
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
})
res.status(200).json({ discount })
} catch (err) {
throw err
}
res.status(200).json({ discount })
}
@@ -1,40 +0,0 @@
import { defaultFields, defaultRelations } from "./"
/**
* @oas [post] /discounts/{id}/products/{product_id}
* operationId: "PostDiscountsDiscountProductsProduct"
* summary: "Adds Product availability"
* description: "Adds a Product to the list of Products that a Discount can be used for."
* parameters:
* - (path) id=* {string} The id of the Discount.
* - (path) product_id=* {string} The id of the Product.
* tags:
* - Discount
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* discount:
* $ref: "#/components/schemas/discount"
*/
export default async (req, res) => {
const { discount_id, variant_id } = req.params
try {
const discountService = req.scope.resolve("discountService")
await discountService.addValidProduct(discount_id, variant_id)
const discount = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ discount })
} catch (err) {
throw err
}
}
@@ -0,0 +1,37 @@
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import { Discount } from "../../../.."
import DiscountService from "../../../../services/discount"
/**
* @oas [post] /discounts/{id}/products/{variant_id}
* operationId: "PostDiscountsDiscountProductsProduct"
* summary: "Adds Product availability"
* description: "Adds a Product to the list of Products that a Discount can be used for."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount.
* - (path) variant_id=* {string} The id of the Product.
* tags:
* - Discount
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* discount:
* $ref: "#/components/schemas/discount"
*/
export default async (req, res) => {
const { discount_id, variant_id } = req.params
const discountService: DiscountService = req.scope.resolve("discountService")
await discountService.addValidProduct(discount_id, variant_id)
const discount: Discount = await discountService.retrieve(discount_id, {
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
})
res.status(200).json({ discount })
}
@@ -1,14 +1,33 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations } from "."
import { Type } from "class-transformer"
import {
IsArray,
IsBoolean,
IsDate,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsPositive,
IsString,
ValidateNested,
} from "class-validator"
import { defaultAdminDiscountsRelations } from "."
import DiscountService from "../../../../services/discount"
import { IsGreaterThan } from "../../../../utils/validators/greater-than"
import { validator } from "../../../../utils/validator"
import { IsISO8601Duration } from "../../../../utils/validators/iso8601-duration"
/**
* @oas [post] /discounts
* operationId: "PostDiscounts"
* summary: "Creates a Discount"
* x-authenticated: true
* description: "Creates a Discount with a given set of rules that define how the Discount behaves."
* requestBody:
* content:
* application/json:
* required:
* - code
* - rule
* schema:
* properties:
* code:
@@ -56,54 +75,84 @@ import { defaultRelations } from "."
* $ref: "#/components/schemas/discount"
*/
export default async (req, res) => {
const schema = Validator.object().keys({
code: Validator.string().required(),
is_dynamic: Validator.boolean().default(false),
rule: Validator.object()
.keys({
description: Validator.string().optional(),
type: Validator.string().required(),
value: Validator.number()
.positive()
.required(),
allocation: Validator.string().required(),
valid_for: Validator.array().items(Validator.string()),
})
.required(),
is_disabled: Validator.boolean().default(false),
starts_at: Validator.date().optional(),
ends_at: Validator.date()
.greater(Validator.ref("starts_at"))
.optional(),
valid_duration: Validator.string()
.isoDuration()
.allow(null)
.optional(),
usage_limit: Validator.number()
.positive()
.optional(),
regions: Validator.array()
.items(Validator.string())
.optional(),
metadata: Validator.object().optional(),
})
const validated = await validator(AdminPostDiscountsReq, req.body)
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const discountService: DiscountService = req.scope.resolve("discountService")
const created = await discountService.create(validated)
const discount = await discountService.retrieve(
created.id,
defaultAdminDiscountsRelations
)
try {
const discountService = req.scope.resolve("discountService")
const created = await discountService.create(value)
const discount = await discountService.retrieve(
created.id,
defaultRelations
)
res.status(200).json({ discount })
} catch (err) {
throw err
}
res.status(200).json({ discount })
}
export class AdminPostDiscountsReq {
@IsString()
@IsNotEmpty()
code: string
@IsNotEmpty()
@ValidateNested()
@Type(() => AdminPostDiscountsDiscountRule)
rule: AdminPostDiscountsDiscountRule
@IsBoolean()
@IsOptional()
is_dynamic = false
@IsBoolean()
@IsOptional()
is_disabled = false
@IsDate()
@IsOptional()
@Type(() => Date)
starts_at?: Date
@IsDate()
@IsOptional()
@IsGreaterThan("starts_at")
@Type(() => Date)
ends_at?: Date
@IsISO8601Duration()
@IsOptional()
valid_duration?: string
@IsNumber()
@IsOptional()
@IsPositive()
usage_limit?: number
@IsArray()
@IsOptional()
@IsString({ each: true })
regions?: string[]
@IsObject()
@IsOptional()
metadata?: object
}
export class AdminPostDiscountsDiscountRule {
@IsString()
@IsOptional()
description?: string
@IsString()
@IsNotEmpty()
type: string
@IsNumber()
value: number
@IsString()
@IsNotEmpty()
allocation: string
@IsOptional()
@IsArray()
@IsString({ each: true })
valid_for?: string[]
}
@@ -1,10 +1,18 @@
import { MedusaError, Validator } from "medusa-core-utils"
import {
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
} from "class-validator"
import DiscountService from "../../../../services/discount"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /discounts/{id}/dynamic-codes
* operationId: "PostDiscountsDiscountDynamicCodes"
* summary: "Create a dynamic Discount code"
* description: "Creates a unique code that can map to a parent Discount. This is useful if you want to automatically generate codes with the same behaviour."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount to create the dynamic code from."
* - (body) code=* {string} The unique code that will be used to redeem the Discount.
@@ -24,27 +32,34 @@ import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { discount_id } = req.params
const schema = Validator.object().keys({
code: Validator.string().required(),
usage_limit: Validator.number().default(1),
metadata: Validator.object().optional(),
const validated = await validator(
AdminPostDiscountsDiscountDynamicCodesReq,
req.body
)
const discountService: DiscountService = req.scope.resolve("discountService")
const created = await discountService.createDynamicCode(
discount_id,
validated
)
const discount = await discountService.retrieve(created.id, {
relations: ["rule", "rule.valid_for", "regions"],
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
res.status(200).json({ discount })
}
try {
const discountService = req.scope.resolve("discountService")
const created = await discountService.createDynamicCode(discount_id, value)
export class AdminPostDiscountsDiscountDynamicCodesReq {
@IsString()
@IsNotEmpty()
code: string
const discount = await discountService.retrieve(created.id, {
relations: ["rule", "rule.valid_for", "regions"],
})
@IsNumber()
@IsOptional()
usage_limit = 1
res.status(200).json({ discount })
} catch (err) {
throw err
}
@IsObject()
@IsOptional()
metadata?: object
}
@@ -1,8 +1,11 @@
import DiscountService from "../../../../services/discount"
/**
* @oas [delete] /discounts/{id}
* operationId: "DeleteDiscountsDiscount"
* summary: "Delete a Discount"
* description: "Deletes a Discount."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount
* tags:
@@ -26,16 +29,12 @@
export default async (req, res) => {
const { discount_id } = req.params
try {
const discountService = req.scope.resolve("discountService")
await discountService.delete(discount_id)
const discountService: DiscountService = req.scope.resolve("discountService")
await discountService.delete(discount_id)
res.json({
id: discount_id,
object: "discount",
deleted: true,
})
} catch (err) {
throw err
}
res.json({
id: discount_id,
object: "discount",
deleted: true,
})
}
@@ -1,8 +1,11 @@
import DiscountService from "../../../../services/discount"
/**
* @oas [delete] /discounts/{id}/dynamic-codes/{code}
* operationId: "DeleteDiscountsDiscountDynamicCodesCode"
* summary: "Delete a dynamic code"
* description: "Deletes a dynamic code from a Discount."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount
* - (path) code=* {string} The id of the Discount
@@ -21,16 +24,12 @@
export default async (req, res) => {
const { discount_id, code } = req.params
try {
const discountService = req.scope.resolve("discountService")
await discountService.deleteDynamicCode(discount_id, code)
const discountService: DiscountService = req.scope.resolve("discountService")
await discountService.deleteDynamicCode(discount_id, code)
const discount = await discountService.retrieve(discount_id, {
relations: ["rule", "rule.valid_for", "regions"],
})
const discount = await discountService.retrieve(discount_id, {
relations: ["rule", "rule.valid_for", "regions"],
})
res.status(200).json({ discount })
} catch (err) {
throw err
}
res.status(200).json({ discount })
}
@@ -1,10 +1,11 @@
import { defaultFields, defaultRelations } from "./"
import { defaultAdminDiscountsRelations } from "."
import DiscountService from "../../../../services/discount"
/**
* @oas [get] /discounts/code/{code}
* operationId: "GetDiscountsDiscountCode"
* summary: "Retrieve a Discount by code"
* description: "Retrieves a Discount by its discount code"
* x-authenticated: true
* parameters:
* - (path) code=* {string} The code of the Discount
* tags:
@@ -21,15 +22,12 @@ import { defaultFields, defaultRelations } from "./"
*/
export default async (req, res) => {
const { code } = req.params
try {
const discountService = req.scope.resolve("discountService")
const discount = await discountService.retrieveByCode(
code,
defaultRelations
)
res.status(200).json({ discount })
} catch (err) {
throw err
}
const discountService: DiscountService = req.scope.resolve("discountService")
const discount = await discountService.retrieveByCode(
code,
defaultAdminDiscountsRelations
)
res.status(200).json({ discount })
}
@@ -1,10 +1,11 @@
import { defaultFields, defaultRelations } from "./"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import DiscountService from "../../../../services/discount"
/**
* @oas [get] /discounts/{id}
* operationId: "GetDiscountsDiscount"
* summary: "Retrieve a Discount"
* description: "Retrieves a Discount"
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount
* tags:
@@ -21,15 +22,12 @@ import { defaultFields, defaultRelations } from "./"
*/
export default async (req, res) => {
const { discount_id } = req.params
try {
const discountService = req.scope.resolve("discountService")
const data = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ discount: data })
} catch (err) {
throw err
}
const discountService: DiscountService = req.scope.resolve("discountService")
const data = await discountService.retrieve(discount_id, {
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
})
res.status(200).json({ discount: data })
}
@@ -1,9 +1,12 @@
import { Router } from "express"
import { Discount } from "../../../.."
import middlewares from "../../../middlewares"
import "reflect-metadata"
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
const route = Router()
export default app => {
export default (app) => {
app.use("/discounts", route)
route.get("/", middlewares.wrap(require("./list-discounts").default))
@@ -59,7 +62,7 @@ export default app => {
return app
}
export const defaultFields = [
export const defaultAdminDiscountsFields = [
"id",
"code",
"is_dynamic",
@@ -77,9 +80,32 @@ export const defaultFields = [
"valid_duration",
]
export const defaultRelations = [
export const defaultAdminDiscountsRelations = [
"rule",
"parent_discount",
"regions",
"rule.valid_for",
]
export type AdminDiscountsRes = {
discount: Discount
}
export type AdminDiscountsDeleteRes = DeleteResponse
export type AdminDiscountsListRes = PaginatedResponse & {
discounts: Discount[]
}
export * from "./add-region"
export * from "./add-valid-product"
export * from "./create-discount"
export * from "./create-dynamic-code"
export * from "./delete-discount"
export * from "./delete-dynamic-code"
export * from "./get-discount"
export * from "./get-discount-by-code"
export * from "./list-discounts"
export * from "./remove-region"
export * from "./remove-valid-product"
export * from "./update-discount"
@@ -1,58 +0,0 @@
import { defaultFields, defaultRelations } from "./"
/**
* @oas [get] /discounts
* operationId: "GetDiscounts"
* summary: "List Discounts"
* description: "Retrieves a list of Discounts"
* tags:
* - Discount
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* discount:
* $ref: "#/components/schemas/discount"
*/
export default async (req, res) => {
try {
const discountService = req.scope.resolve("discountService")
const limit = parseInt(req.query.limit) || 20
const offset = parseInt(req.query.offset) || 0
let selector = {}
if ("q" in req.query) {
selector.q = req.query.q
}
if ("is_dynamic" in req.query) {
selector.is_dynamic = req.query.is_dynamic === "true"
}
if ("is_disabled" in req.query) {
selector.is_disabled = req.query.is_disabled === "true"
}
const listConfig = {
select: defaultFields,
relations: defaultRelations,
skip: offset,
take: limit,
order: { created_at: "DESC" },
}
const [discounts, count] = await discountService.listAndCount(
selector,
listConfig
)
res.status(200).json({ discounts, count, offset, limit })
} catch (err) {
throw err
}
}
@@ -0,0 +1,93 @@
import { Type, Transform } from "class-transformer"
import { IsBoolean, IsInt, IsOptional, IsString } from "class-validator"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import DiscountService from "../../../../services/discount"
import { ListSelector } from "../../../../types/discount"
import { validator } from "../../../../utils/validator"
/**
* @oas [get] /discounts
* operationId: "GetDiscounts"
* summary: "List Discounts"
* x-authenticated: true
* description: "Retrieves a list of Discounts"
* parameters:
* - (query) q {string} Search query applied on results.
* - (query) is_dynamic {boolean} Return only dynamic discounts.
* - (query) is_disabled {boolean} Return only disabled discounts.
* - (query) limit {number} The number of items in the response
* - (query) offset {number} The offset of items in response
* - (query) expand {string} Comma separated list of relations to include in the results.
* tags:
* - Discount
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* discount:
* $ref: "#/components/schemas/discount"
*/
export default async (req, res) => {
const validated = await validator(AdminGetDiscountsParams, req.query)
const discountService: DiscountService = req.scope.resolve("discountService")
const selector: ListSelector = {}
if (validated.q) {
selector.q = validated.q
}
selector.is_disabled = validated.is_disabled
selector.is_dynamic = validated.is_dynamic
const listConfig = {
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
skip: validated.offset,
take: validated.limit,
order: { created_at: "DESC" },
}
const [discounts, count] = await discountService.listAndCount(
selector,
listConfig
)
res.status(200).json({
discounts,
count,
offset: validated.offset,
limit: validated.limit,
})
}
export class AdminGetDiscountsParams {
@IsString()
@IsOptional()
q?: string
@IsBoolean()
@IsOptional()
@Transform(({ value }) => value === "true")
is_dynamic?: boolean
@IsBoolean()
@IsOptional()
@Transform(({ value }) => value === "true")
is_disabled?: boolean
@IsInt()
@IsOptional()
@Type(() => Number)
limit = 20
@IsInt()
@IsOptional()
@Type(() => Number)
offset = 0
@IsString()
@IsOptional()
expand?: string
}
@@ -1,9 +1,10 @@
import { defaultFields, defaultRelations } from "./"
import DiscountService from "../../../../services/discount"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
/**
* @oas [delete] /discounts/{id}/regions/{region_id}
* operationId: "DeleteDiscountsDiscountRegionsRegion"
* summary: "Remove Region availability"
* x-authenticated: true
* description: "Removes a Region from the list of Regions that a Discount can be used in."
* parameters:
* - (path) id=* {string} The id of the Discount.
@@ -23,17 +24,13 @@ import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { discount_id, region_id } = req.params
try {
const discountService = req.scope.resolve("discountService")
const discountService: DiscountService = req.scope.resolve("discountService")
await discountService.removeRegion(discount_id, region_id)
await discountService.removeRegion(discount_id, region_id)
const discount = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
})
const discount = await discountService.retrieve(discount_id, {
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
})
res.status(200).json({ discount })
} catch (err) {
throw err
}
res.status(200).json({ discount })
}
@@ -1,10 +1,11 @@
import { defaultFields, defaultRelations } from "./"
import DiscountService from "../../../../services/discount"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
/**
* @oas [post] /discounts/{id}/products/{product_id}
* operationId: "DeleteDiscountsDiscountProductsProduct"
* summary: "Remove Product availability"
* description: "Removes a Product from the list of Products that a Discount can be used for."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount.
* - (path) product_id=* {string} The id of the Product.
@@ -23,18 +24,13 @@ import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { discount_id, variant_id } = req.params
try {
const discountService = req.scope.resolve("discountService")
const discountService: DiscountService = req.scope.resolve("discountService")
await discountService.removeValidProduct(discount_id, variant_id)
await discountService.removeValidProduct(discount_id, variant_id)
const discount = await discountService.retrieve(discount_id, {
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
})
const discount = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ discount })
} catch (err) {
throw err
}
res.status(200).json({ discount })
}
@@ -1,109 +0,0 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
/**
* @oas [post] /discounts/{id}
* operationId: "PostDiscountsDiscount"
* summary: "Update a Discount"
* description: "Updates a Discount with a given set of rules that define how the Discount behaves."
* parameters:
* - (path) id=* {string} The id of the Discount.
* requestBody:
* content:
* application/json:
* schema:
* properties:
* code:
* type: string
* description: A unique code that will be used to redeem the Discount
* is_dynamic:
* type: string
* description: Whether the Discount should have multiple instances of itself, each with a different code. This can be useful for automatically generated codes that all have to follow a common set of rules.
* rule:
* description: The Discount Rule that defines how Discounts are calculated
* oneOf:
* - $ref: "#/components/schemas/discount_rule"
* is_disabled:
* type: boolean
* description: Whether the Discount code is disabled on creation. You will have to enable it later to make it available to Customers.
* starts_at:
* type: string
* format: date-time
* description: The time at which the Discount should be available.
* ends_at:
* type: string
* format: date-time
* description: The time at which the Discount should no longer be available.
* regions:
* description: A list of Region ids representing the Regions in which the Discount can be used.
* type: array
* items:
* type: string
* tags:
* - Discount
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* discount:
* $ref: "#/components/schemas/discount"
*/
export default async (req, res) => {
const { discount_id } = req.params
const schema = Validator.object().keys({
code: Validator.string().optional(),
is_dynamic: Validator.boolean().default(false),
rule: Validator.object()
.keys({
id: Validator.string().required(),
description: Validator.string().optional(),
type: Validator.string().required(),
value: Validator.number().required(),
allocation: Validator.string().required(),
valid_for: Validator.array().items(Validator.string()),
})
.optional(),
is_disabled: Validator.boolean().optional(),
starts_at: Validator.date().optional(),
ends_at: Validator.when("starts_at", {
not: undefined,
then: Validator.date()
.greater(Validator.ref("starts_at"))
.optional(),
otherwise: Validator.date().optional(),
}),
valid_duration: Validator.string()
.isoDuration().allow(null)
.optional(),
usage_limit: Validator.number()
.positive()
.optional(),
regions: Validator.array()
.items(Validator.string())
.optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const discountService = req.scope.resolve("discountService")
await discountService.update(discount_id, value)
const discount = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ discount })
} catch (err) {
throw err
}
}
@@ -0,0 +1,158 @@
import { Type } from "class-transformer"
import {
IsArray,
IsBoolean,
IsDate,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsPositive,
IsString,
ValidateNested,
} from "class-validator"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import DiscountService from "../../../../services/discount"
import { IsGreaterThan } from "../../../../utils/validators/greater-than"
import { validator } from "../../../../utils/validator"
import { IsISO8601Duration } from "../../../../utils/validators/iso8601-duration"
/**
* @oas [post] /discounts/{id}
* operationId: "PostDiscountsDiscount"
* summary: "Update a Discount"
* description: "Updates a Discount with a given set of rules that define how the Discount behaves."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount.
* requestBody:
* content:
* application/json:
* schema:
* properties:
* code:
* type: string
* description: A unique code that will be used to redeem the Discount
* is_dynamic:
* type: string
* description: Whether the Discount should have multiple instances of itself, each with a different code. This can be useful for automatically generated codes that all have to follow a common set of rules.
* rule:
* description: The Discount Rule that defines how Discounts are calculated
* oneOf:
* - $ref: "#/components/schemas/discount_rule"
* is_disabled:
* type: boolean
* description: Whether the Discount code is disabled on creation. You will have to enable it later to make it available to Customers.
* starts_at:
* type: Date
* description: The time at which the Discount should be available.
* ends_at:
* type: Date
* description: The time at which the Discount should no longer be available.
* regions:
* description: A list of Region ids representing the Regions in which the Discount can be used.
* type: array
* items:
* type: string
* metadata:
* description: An object containing metadata of the discount
* type: object
* tags:
* - Discount
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* discount:
* $ref: "#/components/schemas/discount"
*/
export default async (req, res) => {
const { discount_id } = req.params
const validated = await validator(AdminPostDiscountsDiscountReq, req.body)
const discountService: DiscountService = req.scope.resolve("discountService")
await discountService.update(discount_id, validated)
const discount = await discountService.retrieve(discount_id, {
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
})
res.status(200).json({ discount })
}
export class AdminPostDiscountsDiscountReq {
@IsString()
@IsOptional()
code?: string
@IsOptional()
@ValidateNested()
@Type(() => AdminUpdateDiscountRule)
rule?: AdminUpdateDiscountRule
@IsBoolean()
@IsOptional()
is_dynamic?: boolean
@IsBoolean()
@IsOptional()
is_disabled?: boolean
@IsDate()
@IsOptional()
@Type(() => Date)
starts_at?: Date
@IsDate()
@IsOptional()
@IsGreaterThan("starts_at")
@Type(() => Date)
ends_at?: Date
@IsISO8601Duration()
@IsOptional()
valid_duration?: string
@IsNumber()
@IsOptional()
@IsPositive()
usage_limit?: number
@IsArray()
@IsOptional()
@IsString({ each: true })
regions?: string[]
@IsObject()
@IsOptional()
metadata?: object
}
export class AdminUpdateDiscountRule {
@IsString()
@IsNotEmpty()
id: string
@IsString()
@IsOptional()
description?: string
@IsString()
@IsNotEmpty()
type: string
@IsNumber()
value: string
@IsString()
@IsNotEmpty()
allocation: string
@IsArray()
@IsOptional()
@IsString({ each: true })
valid_for?: string
}
@@ -1,19 +1,40 @@
import { Type } from "class-transformer"
import {
MedusaError,
Validator,
transformIdableFields,
} from "medusa-core-utils"
import { defaultFields, defaultRelations } from "."
IsArray,
IsBoolean,
IsEmail,
IsEnum,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
ValidateNested,
} from "class-validator"
import { transformIdableFields } from "medusa-core-utils"
import {
defaultAdminDraftOrdersFields,
defaultAdminDraftOrdersRelations,
} from "."
import { DraftOrder } from "../../../.."
import { DraftOrderService } from "../../../../services"
import { AddressPayload } from "../../../../types/common"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /draft-orders
* operationId: "PostDraftOrders"
* summary: "Create a Draft Order"
* description: "Creates a Draft Order"
* x-authenticated: true
* requestBody:
* content:
* application/json:
* schema:
* required:
* - email
* - items
* - region_id
* - shipping_methods
* properties:
* status:
* description: "The status of the draft order"
@@ -97,68 +118,115 @@ import { defaultFields, defaultRelations } from "."
*/
export default async (req, res) => {
const schema = Validator.object().keys({
status: Validator.string()
.valid("open", "completed")
.optional(),
email: Validator.string()
.email()
.required(),
billing_address: Validator.address().optional(),
shipping_address: Validator.address().optional(),
items: Validator.array()
.items({
variant_id: Validator.string()
.optional()
.allow(""),
unit_price: Validator.number().optional(),
title: Validator.string()
.optional()
.allow(""),
quantity: Validator.number().required(),
metadata: Validator.object().default({}),
})
.required(),
region_id: Validator.string().required(),
discounts: Validator.array()
.items({
code: Validator.string().required(),
})
.optional(),
customer_id: Validator.string().optional(),
no_notification_order: Validator.boolean().optional(),
shipping_methods: Validator.array()
.items({
option_id: Validator.string().required(),
data: Validator.object().optional(),
price: Validator.number()
.integer()
.integer()
.allow(0)
.optional(),
})
.required(),
metadata: Validator.object().optional(),
const validated = await validator(AdminPostDraftOrdersReq, req.body)
const value = transformIdableFields(validated, [
"shipping_address",
"billing_address",
])
const draftOrderService: DraftOrderService =
req.scope.resolve("draftOrderService")
let draftOrder: DraftOrder = await draftOrderService.create(value)
draftOrder = await draftOrderService.retrieve(draftOrder.id, {
relations: defaultAdminDraftOrdersRelations,
select: defaultAdminDraftOrdersFields,
})
let { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
value = transformIdableFields(value, ["shipping_address", "billing_address"])
try {
const draftOrderService = req.scope.resolve("draftOrderService")
let draftOrder = await draftOrderService.create(value)
draftOrder = await draftOrderService.retrieve(draftOrder.id, {
relations: defaultRelations,
select: defaultFields,
})
res.status(200).json({ draft_order: draftOrder })
} catch (err) {
throw err
}
res.status(200).json({ draft_order: draftOrder })
}
enum Status {
open = "open",
completed = "completed",
}
export class AdminPostDraftOrdersReq {
@IsEnum(Status)
@IsOptional()
status?: string
@IsEmail()
email: string
@IsOptional()
@Type(() => AddressPayload)
billing_address?: AddressPayload
@IsOptional()
@Type(() => AddressPayload)
shipping_address?: AddressPayload
@IsArray()
@Type(() => Item)
@IsNotEmpty()
@ValidateNested({ each: true })
items: Item[]
@IsString()
region_id: string
@IsArray()
@IsOptional()
@Type(() => Discount)
@ValidateNested({ each: true })
discounts?: Discount[]
@IsString()
@IsOptional()
customer_id?: string
@IsBoolean()
@IsOptional()
no_notification_order?: boolean
@IsArray()
@Type(() => ShippingMethod)
@IsNotEmpty()
@ValidateNested({ each: true })
shipping_methods: ShippingMethod[]
@IsObject()
@IsOptional()
metadata?: object = {}
}
class ShippingMethod {
@IsString()
option_id: string
@IsObject()
@IsOptional()
data?: object = {}
@IsNumber()
@IsOptional()
price?: number
}
class Discount {
@IsString()
code: string
}
class Item {
@IsString()
@IsOptional()
title?: string
@IsNumber()
@IsOptional()
unit_price?: number
@IsString()
@IsOptional()
variant_id?: string
@IsNumber()
quantity: number
@IsObject()
@IsOptional()
metadata?: object = {}
}
@@ -1,111 +0,0 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultCartFields, defaultCartRelations, defaultFields } from "."
/**
* @oas [post] /draft-orders/{id}/line-items
* operationId: "PostDraftOrdersDraftOrderLineItems"
* summary: "Create a Line Item for Draft Order"
* description: "Creates a Line Item for the Draft Order"
* requestBody:
* content:
* application/json:
* schema:
* properties:
* variant_id:
* description: The id of the Product Variant to generate the Line Item from.
* type: string
* unit_price:
* description: The potential custom price of the item.
* type: integer
* title:
* description: The potential custom title of the item.
* type: string
* quantity:
* description: The quantity of the Line Item.
* type: integer
* metadata:
* description: The optional key-value map with additional details about the Line Item.
* type: object
* tags:
* - Draft Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* draft_order:
* $ref: "#/components/schemas/draft-order"
*/
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
title: Validator.string().optional(),
unit_price: Validator.number().optional(),
variant_id: Validator.string().optional(),
quantity: Validator.number().required(),
metadata: Validator.object().optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const draftOrderService = req.scope.resolve("draftOrderService")
const cartService = req.scope.resolve("cartService")
const lineItemService = req.scope.resolve("lineItemService")
const entityManager = req.scope.resolve("manager")
await entityManager.transaction(async manager => {
const draftOrder = await draftOrderService
.withTransaction(manager)
.retrieve(id, { select: defaultFields, relations: ["cart"] })
if (draftOrder.status === "completed") {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"You are only allowed to update open draft orders"
)
}
if (value.variant_id) {
const line = await lineItemService.generate(
value.variant_id,
draftOrder.cart.region_id,
value.quantity,
{ metadata: value.metadata, unit_price: value.unit_price }
)
await cartService
.withTransaction(manager)
.addLineItem(draftOrder.cart_id, line)
} else {
// custom line items can be added to a draft order
await lineItemService.withTransaction(manager).create({
cart_id: draftOrder.cart_id,
has_shipping: true,
title: value.title || "Custom item",
allow_discounts: false,
unit_price: value.unit_price || 0,
quantity: value.quantity,
})
}
draftOrder.cart = await cartService
.withTransaction(manager)
.retrieve(draftOrder.cart_id, {
relations: defaultCartRelations,
select: defaultCartFields,
})
res.status(200).json({ draft_order: draftOrder })
})
} catch (err) {
throw err
}
}
@@ -0,0 +1,138 @@
import { IsInt, IsObject, IsOptional, IsString } from "class-validator"
import { MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm"
import {
defaultAdminDraftOrdersCartFields,
defaultAdminDraftOrdersCartRelations,
defaultAdminDraftOrdersFields,
} from "."
import {
CartService,
DraftOrderService,
LineItemService,
} from "../../../../services"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /draft-orders/{id}/line-items
* operationId: "PostDraftOrdersDraftOrderLineItems"
* summary: "Create a Line Item for Draft Order"
* description: "Creates a Line Item for the Draft Order"
* x-authenticated: true
* requestBody:
* content:
* application/json:
* required:
* - quantity
* schema:
* properties:
* variant_id:
* description: The id of the Product Variant to generate the Line Item from.
* type: string
* unit_price:
* description: The potential custom price of the item.
* type: integer
* title:
* description: The potential custom title of the item.
* type: string
* quantity:
* description: The quantity of the Line Item.
* type: integer
* metadata:
* description: The optional key-value map with additional details about the Line Item.
* type: object
* tags:
* - Draft Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* draft_order:
* $ref: "#/components/schemas/draft-order"
*/
export default async (req, res) => {
const { id } = req.params
const validated = await validator(
AdminPostDraftOrdersDraftOrderLineItemsReq,
req.body
)
const draftOrderService: DraftOrderService =
req.scope.resolve("draftOrderService")
const cartService: CartService = req.scope.resolve("cartService")
const lineItemService: LineItemService = req.scope.resolve("lineItemService")
const entityManager: EntityManager = req.scope.resolve("manager")
await entityManager.transaction(async (manager) => {
const draftOrder = await draftOrderService
.withTransaction(manager)
.retrieve(id, {
select: defaultAdminDraftOrdersFields,
relations: ["cart"],
})
if (draftOrder.status === "completed") {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"You are only allowed to update open draft orders"
)
}
if (validated.variant_id) {
const line = await lineItemService.generate(
validated.variant_id,
draftOrder.cart.region_id,
validated.quantity,
{ metadata: validated.metadata, unit_price: validated.unit_price }
)
await cartService
.withTransaction(manager)
.addLineItem(draftOrder.cart_id, line)
} else {
// custom line items can be added to a draft order
await lineItemService.withTransaction(manager).create({
cart_id: draftOrder.cart_id,
has_shipping: true,
title: validated.title,
allow_discounts: false,
unit_price: validated.unit_price || 0,
quantity: validated.quantity,
})
}
draftOrder.cart = await cartService
.withTransaction(manager)
.retrieve(draftOrder.cart_id, {
relations: defaultAdminDraftOrdersCartRelations,
select: defaultAdminDraftOrdersCartFields,
})
res.status(200).json({ draft_order: draftOrder })
})
}
export class AdminPostDraftOrdersDraftOrderLineItemsReq {
@IsString()
@IsOptional()
title?: string = "Custom item"
@IsInt()
@IsOptional()
unit_price?: number
@IsString()
@IsOptional()
variant_id?: string
@IsInt()
quantity: number
@IsObject()
@IsOptional()
metadata?: object = {}
}
@@ -1,8 +1,10 @@
import { DraftOrderService } from "../../../../services"
/**
* @oas [delete] /draft-orders/{id}
* operationId: DeleteDraftOrdersDraftOrder
* summary: Delete a Draft Order
* description: "Deletes a Draft Order"
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Draft Order.
* tags:
@@ -23,19 +25,16 @@
* deleted:
* type: boolean
*/
export default async (req, res) => {
const { id } = req.params
try {
const draftOrderService = req.scope.resolve("draftOrderService")
await draftOrderService.delete(id)
res.json({
id,
object: "draft-order",
deleted: true,
})
} catch (err) {
throw err
}
const draftOrderService: DraftOrderService =
req.scope.resolve("draftOrderService")
await draftOrderService.delete(id)
res.json({
id,
object: "draft-order",
deleted: true,
})
}
@@ -1,61 +0,0 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultCartFields, defaultCartRelations, defaultFields } from "."
/**
* @oas [delete] /draft-orders/{id}/line-items/{line_id}
* operationId: DeleteDraftOrdersDraftOrderLineItemsItem
* summary: Delete a Line Item
* description: "Removes a Line Item from a Draft Order."
* parameters:
* - (path) id=* {string} The id of the Draft Order.
* - (path) line_id=* {string} The id of the Draft Order.
* tags:
* - Draft Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* draft_order:
* $ref: "#/components/schemas/draft-order"
*/
export default async (req, res) => {
const { id, line_id } = req.params
try {
const draftOrderService = req.scope.resolve("draftOrderService")
const cartService = req.scope.resolve("cartService")
const entityManager = req.scope.resolve("manager")
await entityManager.transaction(async manager => {
const draftOrder = await draftOrderService
.withTransaction(manager)
.retrieve(id, { select: defaultFields })
if (draftOrder.status === "completed") {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"You are only allowed to update open draft orders"
)
}
await cartService
.withTransaction(manager)
.removeLineItem(draftOrder.cart_id, line_id)
draftOrder.cart = await cartService
.withTransaction(manager)
.retrieve(draftOrder.cart_id, {
relations: defaultCartRelations,
select: defaultCartFields,
})
res.status(200).json({ draft_order: draftOrder })
})
} catch (err) {
throw err
}
}
@@ -0,0 +1,65 @@
import { MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm"
import {
defaultAdminDraftOrdersCartFields,
defaultAdminDraftOrdersCartRelations,
defaultAdminDraftOrdersFields,
} from "."
import { DraftOrder } from "../../../.."
import { CartService, DraftOrderService } from "../../../../services"
/**
* @oas [delete] /draft-orders/{id}/line-items/{line_id}
* operationId: DeleteDraftOrdersDraftOrderLineItemsItem
* summary: Delete a Line Item
* description: "Removes a Line Item from a Draft Order."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Draft Order.
* - (path) line_id=* {string} The id of the Draft Order.
* tags:
* - Draft Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* draft_order:
* $ref: "#/components/schemas/draft-order"
*/
export default async (req, res) => {
const { id, line_id } = req.params
const draftOrderService: DraftOrderService =
req.scope.resolve("draftOrderService")
const cartService: CartService = req.scope.resolve("cartService")
const entityManager: EntityManager = req.scope.resolve("manager")
await entityManager.transaction(async (manager) => {
const draftOrder: DraftOrder = await draftOrderService
.withTransaction(manager)
.retrieve(id, { select: defaultAdminDraftOrdersFields })
if (draftOrder.status === "completed") {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"You are only allowed to update open draft orders"
)
}
await cartService
.withTransaction(manager)
.removeLineItem(draftOrder.cart_id, line_id)
draftOrder.cart = await cartService
.withTransaction(manager)
.retrieve(draftOrder.cart_id, {
relations: defaultAdminDraftOrdersCartRelations,
select: defaultAdminDraftOrdersCartFields,
})
res.status(200).json({ draft_order: draftOrder })
})
}
@@ -1,49 +0,0 @@
import {
defaultRelations,
defaultFields,
defaultCartRelations,
defaultCartFields,
} from "."
/**
* @oas [get] /draft-orders/{id}
* operationId: "GetDraftOrdersDraftOrder"
* summary: "Retrieve a Draft Order"
* description: "Retrieves a Draft Order."
* parameters:
* - (path) id=* {string} The id of the Draft Order.
* tags:
* - Draft Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* draft_order:
* $ref: "#/components/schemas/draft-order"
*/
export default async (req, res) => {
const { id } = req.params
try {
const draftOrderService = req.scope.resolve("draftOrderService")
const cartService = req.scope.resolve("cartService")
const draftOrder = await draftOrderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
draftOrder.cart = await cartService.retrieve(draftOrder.cart_id, {
relations: defaultCartRelations,
select: defaultCartFields,
})
res.json({ draft_order: draftOrder })
} catch (error) {
throw error
}
}
@@ -0,0 +1,49 @@
import {
defaultAdminDraftOrdersRelations,
defaultAdminDraftOrdersFields,
defaultAdminDraftOrdersCartRelations,
defaultAdminDraftOrdersCartFields,
} from "."
import { DraftOrder } from "../../../.."
import { CartService, DraftOrderService } from "../../../../services"
/**
* @oas [get] /draft-orders/{id}
* operationId: "GetDraftOrdersDraftOrder"
* summary: "Retrieve a Draft Order"
* description: "Retrieves a Draft Order."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Draft Order.
* tags:
* - Draft Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* draft_order:
* $ref: "#/components/schemas/draft-order"
*/
export default async (req, res) => {
const { id } = req.params
const draftOrderService: DraftOrderService =
req.scope.resolve("draftOrderService")
const cartService: CartService = req.scope.resolve("cartService")
const draftOrder: DraftOrder = await draftOrderService.retrieve(id, {
select: defaultAdminDraftOrdersFields,
relations: defaultAdminDraftOrdersRelations,
})
draftOrder.cart = await cartService.retrieve(draftOrder.cart_id, {
relations: defaultAdminDraftOrdersCartRelations,
select: defaultAdminDraftOrdersCartFields,
})
res.json({ draft_order: draftOrder })
}
@@ -1,9 +1,11 @@
import { Router } from "express"
import { DraftOrder, Order } from "../../../.."
import middlewares from "../../../middlewares"
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
const route = Router()
export default app => {
export default (app) => {
app.use("/draft-orders", route)
route.get("/", middlewares.wrap(require("./list-draft-orders").default))
@@ -44,9 +46,9 @@ export default app => {
return app
}
export const defaultRelations = ["order", "cart"]
export const defaultAdminDraftOrdersRelations = ["order", "cart"]
export const defaultCartRelations = [
export const defaultAdminDraftOrdersCartRelations = [
"region",
"items",
"payment",
@@ -60,7 +62,7 @@ export const defaultCartRelations = [
"discounts.rule",
]
export const defaultCartFields = [
export const defaultAdminDraftOrdersCartFields = [
"subtotal",
"tax_total",
"shipping_total",
@@ -69,7 +71,7 @@ export const defaultCartFields = [
"total",
]
export const defaultFields = [
export const defaultAdminDraftOrdersFields = [
"id",
"status",
"display_id",
@@ -82,7 +84,7 @@ export const defaultFields = [
"no_notification_order",
]
export const allowedFields = [
export const allowedAdminDraftOrdersFields = [
"id",
"status",
"display_id",
@@ -94,4 +96,28 @@ export const allowedFields = [
"no_notification_order",
]
export const allowedRelations = ["cart"]
export const allowedAdminDraftOrdersRelations = ["cart"]
export type AdminPostDraftOrdersDraftOrderRegisterPaymentRes = {
order: Order
}
export type AdminDraftOrdersRes = {
draft_order: DraftOrder
}
export type AdminDraftOrdersDeleteRes = DeleteResponse
export type AdminDraftOrdersListRes = PaginatedResponse & {
draft_orders: DraftOrder[]
}
export * from "./create-draft-order"
export * from "./create-line-item"
export * from "./delete-draft-order"
export * from "./delete-line-item"
export * from "./get-draft-order"
export * from "./list-draft-orders"
export * from "./register-payment"
export * from "./update-draft-order"
export * from "./update-line-item"
@@ -1,52 +0,0 @@
import _ from "lodash"
import { defaultFields, defaultRelations } from "./"
/**
* @oas [get] /draft-orders
* operationId: "GetDraftOrders"
* summary: "List Draft Orders"
* description: "Retrieves an list of Draft Orders"
* tags:
* - Draft Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* draft_order:
* $ref: "#/components/schemas/draft-order"
*/
export default async (req, res) => {
try {
const draftOrderService = req.scope.resolve("draftOrderService")
const limit = parseInt(req.query.limit) || 50
const offset = parseInt(req.query.offset) || 0
let selector = {}
if ("q" in req.query) {
selector.q = req.query.q
}
const listConfig = {
select: defaultFields,
relations: defaultRelations,
skip: offset,
take: limit,
order: { created_at: "DESC" },
}
const [draftOrders, count] = await draftOrderService.listAndCount(
selector,
listConfig
)
res.json({ draft_orders: draftOrders, count, offset, limit })
} catch (error) {
throw error
}
}
@@ -0,0 +1,76 @@
import {
defaultAdminDraftOrdersFields,
defaultAdminDraftOrdersRelations,
} from "."
import { DraftOrderService } from "../../../../services"
import { IsNumber, IsOptional, IsString } from "class-validator"
import { validator } from "../../../../utils/validator"
import { Type } from "class-transformer"
import { DraftOrderListSelector } from "../../../../types/draft-orders"
/**
* @oas [get] /draft-orders
* operationId: "GetDraftOrders"
* summary: "List Draft Orders"
* description: "Retrieves an list of Draft Orders"
* x-authenticated: true
* tags:
* - Draft Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* draft_order:
* $ref: "#/components/schemas/draft-order"
*/
export default async (req, res) => {
const draftOrderService: DraftOrderService =
req.scope.resolve("draftOrderService")
const validated = await validator(AdminGetDraftOrdersParams, req.query)
const selector: DraftOrderListSelector = {}
if (validated.q) {
selector.q = validated.q
}
const listConfig = {
select: defaultAdminDraftOrdersFields,
relations: defaultAdminDraftOrdersRelations,
skip: validated.offset,
take: validated.limit,
order: { created_at: "DESC" },
}
const [draftOrders, count] = await draftOrderService.listAndCount(
selector,
listConfig
)
res.json({
draft_orders: draftOrders,
count,
offset: validated.offset,
limit: validated.limit,
})
}
export class AdminGetDraftOrdersParams {
@IsString()
@IsOptional()
q?: string
@IsNumber()
@IsOptional()
@Type(() => Number)
limit?: number = 50
@IsNumber()
@IsOptional()
@Type(() => Number)
offset?: number = 0
}
@@ -1,84 +0,0 @@
import {
defaultFields as defaultOrderFields,
defaultRelations as defaultOrderRelations,
} from "../orders/index"
/**
* @oas [post] /draft-orders/{id}/register-payment
* summary: "Registers a payment for a Draft Order"
* operationId: "PostDraftOrdersDraftOrderRegisterPayment"
* description: "Registers a payment for a Draft Order."
* parameters:
* - (path) id=* {String} The Draft Order id.
* tags:
* - Draft Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* draft_order:
* $ref: "#/components/schemas/draft-order"
*/
export default async (req, res) => {
const { id } = req.params
try {
const draftOrderService = req.scope.resolve("draftOrderService")
const paymentProviderService = req.scope.resolve("paymentProviderService")
const orderService = req.scope.resolve("orderService")
const cartService = req.scope.resolve("cartService")
const entityManager = req.scope.resolve("manager")
let result
await entityManager.transaction(async manager => {
const draftOrder = await draftOrderService
.withTransaction(manager)
.retrieve(id)
const cart = await cartService
.withTransaction(manager)
.retrieve(draftOrder.cart_id, {
select: ["total"],
relations: [
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"shipping_methods",
"region",
"items",
],
})
await paymentProviderService
.withTransaction(manager)
.createSession("system", cart)
await cartService
.withTransaction(manager)
.setPaymentSession(cart.id, "system")
await cartService.withTransaction(manager).authorizePayment(cart.id)
result = await orderService
.withTransaction(manager)
.createFromCart(cart.id)
await draftOrderService
.withTransaction(manager)
.registerCartCompletion(draftOrder.id, result.id)
})
const order = await orderService.retrieve(result.id, {
relations: defaultOrderRelations,
select: defaultOrderFields,
})
res.status(200).json({ order })
} catch (err) {
throw err
}
}
@@ -0,0 +1,89 @@
import { EntityManager } from "typeorm"
import {
CartService,
DraftOrderService,
OrderService,
PaymentProviderService,
} from "../../../../services"
import {
defaultAdminOrdersFields as defaultOrderFields,
defaultAdminOrdersRelations as defaultOrderRelations,
} from "../orders/index"
/**
* @oas [post] /draft-orders/{id}/register-payment
* summary: "Registers a payment for a Draft Order"
* operationId: "PostDraftOrdersDraftOrderRegisterPayment"
* description: "Registers a payment for a Draft Order."
* x-authenticated: true
* parameters:
* - (path) id=* {String} The Draft Order id.
* tags:
* - Draft Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* draft_order:
* $ref: "#/components/schemas/draft-order"
*/
export default async (req, res) => {
const { id } = req.params
const draftOrderService: DraftOrderService =
req.scope.resolve("draftOrderService")
const paymentProviderService: PaymentProviderService = req.scope.resolve(
"paymentProviderService"
)
const orderService: OrderService = req.scope.resolve("orderService")
const cartService: CartService = req.scope.resolve("cartService")
const entityManager: EntityManager = req.scope.resolve("manager")
let result
await entityManager.transaction(async (manager) => {
const draftOrder = await draftOrderService
.withTransaction(manager)
.retrieve(id)
const cart = await cartService
.withTransaction(manager)
.retrieve(draftOrder.cart_id, {
select: ["total"],
relations: [
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"shipping_methods",
"region",
"items",
],
})
await paymentProviderService
.withTransaction(manager)
.createSession("system", cart)
await cartService
.withTransaction(manager)
.setPaymentSession(cart.id, "system")
await cartService.withTransaction(manager).authorizePayment(cart.id)
result = await orderService.withTransaction(manager).createFromCart(cart.id)
await draftOrderService
.withTransaction(manager)
.registerCartCompletion(draftOrder.id, result.id)
})
const order = await orderService.retrieve(result.id, {
relations: defaultOrderRelations,
select: defaultOrderFields,
})
res.status(200).json({ order })
}
@@ -1,113 +0,0 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultCartFields, defaultCartRelations, defaultFields } from "."
/**
* @oas [post] /admin/draft-orders/{id}
* operationId: PostDraftOrdersDraftOrder
* summary: Update a Draft Order"
* description: "Updates a Draft Order."
* parameters:
* - (path) id=* {string} The id of the Draft Order.
* requestBody:
* content:
* application/json:
* schema:
* properties:
* region_id:
* type: string
* description: The id of the Region to create the Draft Order in.
* email:
* type: string
* description: "An email to be used on the Draft Order."
* billing_address:
* description: "The Address to be used for billing purposes."
* anyOf:
* - $ref: "#/components/schemas/address"
* shipping_address:
* description: "The Address to be used for shipping."
* anyOf:
* - $ref: "#/components/schemas/address"
* discounts:
* description: "An array of Discount codes to add to the Draft Order."
* type: array
* items:
* properties:
* code:
* description: "The code that a Discount is identifed by."
* type: string
* no_notification_order:
* description: "An optional flag passed to the resulting order to determine use of notifications."
* type: boolean
* customer_id:
* description: "The id of the Customer to associate the Draft Order with."
* type: string
* tags:
* - Draft Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* draft_order:
* $ref: "#/components/schemas/draft-order"
*/
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
region_id: Validator.string().optional(),
country_code: Validator.string().optional(),
email: Validator.string()
.email()
.optional(),
billing_address: Validator.object().optional(),
shipping_address: Validator.object().optional(),
discounts: Validator.array()
.items({
code: Validator.string(),
})
.optional(),
customer_id: Validator.string().optional(),
no_notification_order: Validator.boolean().optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const draftOrderService = req.scope.resolve("draftOrderService")
const cartService = req.scope.resolve("cartService")
const draftOrder = await draftOrderService.retrieve(id)
if (draftOrder.status === "completed") {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"You are only allowed to update open draft orders"
)
}
if ("no_notification_order" in value) {
await draftOrderService.update(draftOrder.id, {
no_notification_order: value.no_notification_order,
})
delete value.no_notification_order
}
await cartService.update(draftOrder.cart_id, value)
draftOrder.cart = await cartService.retrieve(draftOrder.cart_id, {
relations: defaultCartRelations,
select: defaultCartFields,
})
res.status(200).json({ draft_order: draftOrder })
} catch (err) {
throw err
}
}
@@ -0,0 +1,146 @@
import { MedusaError } from "medusa-core-utils"
import {
defaultAdminDraftOrdersCartFields,
defaultAdminDraftOrdersCartRelations,
} from "."
import {
IsArray,
IsBoolean,
IsEmail,
IsOptional,
IsString,
ValidateNested,
} from "class-validator"
import { CartService, DraftOrderService } from "../../../../services"
import { Type } from "class-transformer"
import { AddressPayload } from "../../../../types/common"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /admin/draft-orders/{id}
* operationId: PostDraftOrdersDraftOrder
* summary: Update a Draft Order"
* description: "Updates a Draft Order."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Draft Order.
* requestBody:
* content:
* application/json:
* schema:
* properties:
* region_id:
* type: string
* description: The id of the Region to create the Draft Order in.
* email:
* type: string
* description: "An email to be used on the Draft Order."
* billing_address:
* description: "The Address to be used for billing purposes."
* anyOf:
* - $ref: "#/components/schemas/address"
* shipping_address:
* description: "The Address to be used for shipping."
* anyOf:
* - $ref: "#/components/schemas/address"
* discounts:
* description: "An array of Discount codes to add to the Draft Order."
* type: array
* items:
* properties:
* code:
* description: "The code that a Discount is identifed by."
* type: string
* no_notification_order:
* description: "An optional flag passed to the resulting order to determine use of notifications."
* type: boolean
* customer_id:
* description: "The id of the Customer to associate the Draft Order with."
* type: string
* tags:
* - Draft Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* draft_order:
* $ref: "#/components/schemas/draft-order"
*/
export default async (req, res) => {
const { id } = req.params
const validated = await validator(AdminPostDraftOrdersDraftOrderReq, req.body)
const draftOrderService: DraftOrderService =
req.scope.resolve("draftOrderService")
const cartService: CartService = req.scope.resolve("cartService")
const draftOrder = await draftOrderService.retrieve(id)
if (draftOrder.status === "completed") {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"You are only allowed to update open draft orders"
)
}
if (validated.no_notification_order !== undefined) {
await draftOrderService.update(draftOrder.id, {
no_notification_order: validated.no_notification_order,
})
delete validated.no_notification_order
}
await cartService.update(draftOrder.cart_id, validated)
draftOrder.cart = await cartService.retrieve(draftOrder.cart_id, {
relations: defaultAdminDraftOrdersCartRelations,
select: defaultAdminDraftOrdersCartFields,
})
res.status(200).json({ draft_order: draftOrder })
}
export class AdminPostDraftOrdersDraftOrderReq {
@IsString()
@IsOptional()
region_id?: string
@IsString()
@IsOptional()
country_code?: string
@IsEmail()
@IsOptional()
email?: string
@IsOptional()
@Type(() => AddressPayload)
billing_address?: AddressPayload
@IsOptional()
@Type(() => AddressPayload)
shipping_address?: AddressPayload
@IsArray()
@IsOptional()
@Type(() => Discount)
@ValidateNested({ each: true })
discounts?: Discount[]
@IsString()
@IsOptional()
customer_id?: string
@IsBoolean()
@IsOptional()
no_notification_order?: boolean
}
class Discount {
@IsString()
code: string
}
@@ -1,114 +0,0 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultCartFields, defaultCartRelations, defaultFields } from "."
/**
* @oas [post] /draft-orders/{id}/line-items/{line_id}
* operationId: "PostDraftOrdersDraftOrderLineItemsItem"
* summary: "Update a Line Item for a Draft Order"
* description: "Updates a Line Item for a Draft Order"
* requestBody:
* content:
* application/json:
* schema:
* properties:
* unit_price:
* description: The potential custom price of the item.
* type: integer
* title:
* description: The potential custom title of the item.
* type: string
* quantity:
* description: The quantity of the Line Item.
* type: integer
* metadata:
* description: The optional key-value map with additional details about the Line Item.
* type: object
* tags:
* - Draft Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* draft_order:
* $ref: "#/components/schemas/draft-order"
*/
export default async (req, res) => {
const { id, line_id } = req.params
const schema = Validator.object().keys({
title: Validator.string().optional(),
unit_price: Validator.number().optional(),
quantity: Validator.number().optional(),
metadata: Validator.object().optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const draftOrderService = req.scope.resolve("draftOrderService")
const cartService = req.scope.resolve("cartService")
const entityManager = req.scope.resolve("manager")
await entityManager.transaction(async manager => {
const draftOrder = await draftOrderService
.withTransaction(manager)
.retrieve(id, {
select: defaultFields,
relations: ["cart", "cart.items"],
})
if (draftOrder.status === "completed") {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"You are only allowed to update open draft orders"
)
}
if (value.quantity === 0) {
await cartService
.withTransaction(manager)
.removeLineItem(draftOrder.cart.id, line_id)
} else {
const existing = draftOrder.cart.items.find(i => i.id === line_id)
if (!existing) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Could not find the line item"
)
}
const lineItemUpdate = {
...value,
region_id: draftOrder.cart.region_id,
}
if (existing.variant_id) {
lineItemUpdate.variant_id = existing.variant_id
}
await cartService
.withTransaction(manager)
.updateLineItem(draftOrder.cart_id, line_id, lineItemUpdate)
}
draftOrder.cart = await cartService
.withTransaction(manager)
.retrieve(draftOrder.cart_id, {
relations: defaultCartRelations,
select: defaultCartFields,
})
res.status(200).json({ draft_order: draftOrder })
})
} catch (err) {
throw err
}
}
@@ -0,0 +1,140 @@
import { IsInt, IsObject, IsOptional, IsString } from "class-validator"
import { MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm"
import {
defaultAdminDraftOrdersCartFields,
defaultAdminDraftOrdersCartRelations,
defaultAdminDraftOrdersFields,
} from "."
import { DraftOrder } from "../../../.."
import { CartService, DraftOrderService } from "../../../../services"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /draft-orders/{id}/line-items/{line_id}
* operationId: "PostDraftOrdersDraftOrderLineItemsItem"
* summary: "Update a Line Item for a Draft Order"
* description: "Updates a Line Item for a Draft Order"
* x-authenticated: true
* requestBody:
* content:
* application/json:
* schema:
* properties:
* unit_price:
* description: The potential custom price of the item.
* type: integer
* title:
* description: The potential custom title of the item.
* type: string
* quantity:
* description: The quantity of the Line Item.
* type: integer
* metadata:
* description: The optional key-value map with additional details about the Line Item.
* type: object
* tags:
* - Draft Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* draft_order:
* $ref: "#/components/schemas/draft-order"
*/
export default async (req, res) => {
const { id, line_id } = req.params
const validated = await validator(
AdminPostDraftOrdersDraftOrderLineItemsItemReq,
req.body
)
const draftOrderService: DraftOrderService =
req.scope.resolve("draftOrderService")
const cartService: CartService = req.scope.resolve("cartService")
const entityManager: EntityManager = req.scope.resolve("manager")
await entityManager.transaction(async (manager) => {
const draftOrder: DraftOrder = await draftOrderService
.withTransaction(manager)
.retrieve(id, {
select: defaultAdminDraftOrdersFields,
relations: ["cart", "cart.items"],
})
if (draftOrder.status === "completed") {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"You are only allowed to update open draft orders"
)
}
if (validated.quantity === 0) {
await cartService
.withTransaction(manager)
.removeLineItem(draftOrder.cart.id, line_id)
} else {
const existing = draftOrder.cart.items.find((i) => i.id === line_id)
if (!existing) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Could not find the line item"
)
}
const lineItemUpdate: LineItemUpdate = {
...validated,
region_id: draftOrder.cart.region_id,
}
if (existing.variant_id) {
lineItemUpdate.variant_id = existing.variant_id
}
await cartService
.withTransaction(manager)
.updateLineItem(draftOrder.cart_id, line_id, lineItemUpdate)
}
draftOrder.cart = await cartService
.withTransaction(manager)
.retrieve(draftOrder.cart_id, {
relations: defaultAdminDraftOrdersCartRelations,
select: defaultAdminDraftOrdersCartFields,
})
res.status(200).json({ draft_order: draftOrder })
})
}
class LineItemUpdate {
title?: string
unit_price?: number
quantity?: number
metadata?: object = {}
region_id?: string
variant_id?: string
}
export class AdminPostDraftOrdersDraftOrderLineItemsItemReq {
@IsString()
@IsOptional()
title?: string
@IsInt()
@IsOptional()
unit_price?: number
@IsInt()
@IsOptional()
quantity?: number
@IsObject()
@IsOptional()
metadata?: object = {}
}
@@ -1,11 +1,15 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
import { Type } from "class-transformer"
import { IsBoolean, IsDate, IsInt, IsOptional, IsString } from "class-validator"
import { defaultAdminGiftCardFields, defaultAdminGiftCardRelations } from "."
import { GiftCardService } from "../../../../services"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /gift-cards
* operationId: "PostGiftCards"
* summary: "Create a Gift Card"
* description: "Creates a Gift Card that can redeemed by its unique code. The Gift Card is only valid within 1 region."
* x-authenticated: true
* requestBody:
* content:
* application/json:
@@ -42,36 +46,41 @@ import { defaultFields, defaultRelations } from "./"
* $ref: "#/components/schemas/gift_card"
*/
export default async (req, res) => {
const schema = Validator.object().keys({
value: Validator.number()
.integer()
.optional(),
ends_at: Validator.date().optional(),
is_disabled: Validator.boolean().optional(),
region_id: Validator.string().optional(),
metadata: Validator.object().optional(),
const validated = await validator(AdminPostGiftCardsReq, req.body)
const giftCardService: GiftCardService = req.scope.resolve("giftCardService")
const newly = await giftCardService.create({
...validated,
balance: validated.value,
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const giftCard = await giftCardService.retrieve(newly.id, {
select: defaultAdminGiftCardFields,
relations: defaultAdminGiftCardRelations,
})
try {
const giftCardService = req.scope.resolve("giftCardService")
res.status(200).json({ gift_card: giftCard })
}
const newly = await giftCardService.create({
...value,
balance: value.value,
})
export class AdminPostGiftCardsReq {
@IsOptional()
@IsInt()
value?: number
const giftCard = await giftCardService.retrieve(newly.id, {
select: defaultFields,
relations: defaultRelations,
})
@IsOptional()
@IsDate()
@Type(() => Date)
ends_at?: Date
res.status(200).json({ gift_card: giftCard })
} catch (err) {
throw err
}
@IsOptional()
@IsBoolean()
is_disabled?: boolean
@IsOptional()
@IsString()
region_id?: string
@IsOptional()
metadata?: object
}
@@ -3,6 +3,7 @@
* operationId: "DeleteGiftCardsGiftCard"
* summary: "Delete a Gift Card"
* description: "Deletes a Gift Card"
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Gift Card to delete.
* tags:
@@ -26,16 +27,12 @@
export default async (req, res) => {
const { id } = req.params
try {
const giftCardService = req.scope.resolve("giftCardService")
await giftCardService.delete(id)
const giftCardService = req.scope.resolve("giftCardService")
await giftCardService.delete(id)
res.json({
id,
object: "gift-card",
deleted: true,
})
} catch (err) {
throw err
}
res.json({
id,
object: "gift-card",
deleted: true,
})
}
@@ -1,10 +1,11 @@
import { defaultFields, defaultRelations } from "./"
import { defaultAdminGiftCardFields, defaultAdminGiftCardRelations } from "./"
/**
* @oas [get] /gift-cards/{id}
* operationId: "GetGiftCardsGiftCard"
* summary: "Retrieve a Gift Card"
* description: "Retrieves a Gift Card."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Gift Card.
* tags:
@@ -22,15 +23,11 @@ import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id } = req.params
try {
const giftCardService = req.scope.resolve("giftCardService")
const giftCard = await giftCardService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
const giftCardService = req.scope.resolve("giftCardService")
const giftCard = await giftCardService.retrieve(id, {
select: defaultAdminGiftCardFields,
relations: defaultAdminGiftCardRelations,
})
res.status(200).json({ gift_card: giftCard })
} catch (err) {
throw err
}
res.status(200).json({ gift_card: giftCard })
}
@@ -1,9 +1,12 @@
import { Router } from "express"
import "reflect-metadata"
import { GiftCard } from "../../../.."
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
export default (app) => {
app.use("/gift-cards", route)
route.get("/", middlewares.wrap(require("./list-gift-cards").default))
@@ -19,7 +22,7 @@ export default app => {
return app
}
export const defaultFields = [
export const defaultAdminGiftCardFields = [
"id",
"code",
"value",
@@ -33,12 +36,9 @@ export const defaultFields = [
"metadata",
]
export const defaultRelations = [
"region",
"order",
]
export const defaultAdminGiftCardRelations = ["region", "order"]
export const allowedFields = [
export const allowedAdminGiftCardFields = [
"id",
"code",
"value",
@@ -52,4 +52,18 @@ export const allowedFields = [
"metadata",
]
export const allowedRelations = ["region"]
export const allowedAdminGiftCardRelations = ["region"]
export type AdminGiftCardsRes = {
gift_card: GiftCard
}
export type AdminGiftCardsDeleteRes = DeleteResponse
export type AdminGiftCardsListRes = PaginatedResponse & {
gift_cards: GiftCard[]
}
export * from "./create-gift-card"
export * from "./list-gift-cards"
export * from "./update-gift-card"
@@ -1,48 +0,0 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
/**
* @oas [get] /gift-cards
* operationId: "GetGiftCards"
* summary: "List Gift Cards"
* description: "Retrieves a list of Gift Cards."
* tags:
* - Gift Card
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* gift_cards:
* type: array
* items:
* $ref: "#/components/schemas/gift_card"
*/
export default async (req, res) => {
try {
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
}
const giftCardService = req.scope.resolve("giftCardService")
const giftCards = await giftCardService.list(selector, {
select: defaultFields,
relations: defaultRelations,
order: { created_at: "DESC" },
limit: limit,
skip: offset,
})
res.status(200).json({ gift_cards: giftCards })
} catch (err) {
throw err
}
}
@@ -0,0 +1,68 @@
import { Type } from "class-transformer"
import { IsInt, IsOptional, IsString } from "class-validator"
import { defaultAdminGiftCardFields, defaultAdminGiftCardRelations } from "."
import { GiftCardService } from "../../../../services"
import { validator } from "../../../../utils/validator"
/**
* @oas [get] /gift-cards
* operationId: "GetGiftCards"
* summary: "List Gift Cards"
* description: "Retrieves a list of Gift Cards."
* x-authenticated: true
* tags:
* - Gift Card
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* gift_cards:
* type: array
* items:
* $ref: "#/components/schemas/gift_card"
*/
export default async (req, res) => {
const validated = await validator(AdminGetGiftCardsParams, req.query)
const selector = {}
if (validated.q && typeof validated.q !== "undefined") {
selector["q"] = validated.q
}
const giftCardService: GiftCardService = req.scope.resolve("giftCardService")
const giftCards = await giftCardService.list(selector, {
select: defaultAdminGiftCardFields,
relations: defaultAdminGiftCardRelations,
order: { created_at: "DESC" },
limit: validated.limit,
skip: validated.offset,
})
res.status(200).json({
gift_cards: giftCards,
count: giftCards.length,
offset: validated.offset,
limit: validated.limit,
})
}
export class AdminGetGiftCardsParams {
@IsOptional()
@IsInt()
@Type(() => Number)
limit = 50
@IsOptional()
@IsInt()
@Type(() => Number)
offset = 0
@IsOptional()
@IsString()
q?: string
}
@@ -1,11 +1,15 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
import { Type } from "class-transformer"
import { IsBoolean, IsDate, IsInt, IsOptional, IsString } from "class-validator"
import { defaultAdminGiftCardFields, defaultAdminGiftCardRelations } from "."
import { GiftCardService } from "../../../../services"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /gift-cards/{id}
* operationId: "PostGiftCardsGiftCard"
* summary: "Create a Gift Card"
* description: "Creates a Gift Card that can redeemed by its unique code. The Gift Card is only valid within 1 region."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Gift Card.
* requestBody:
@@ -46,33 +50,38 @@ import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
balance: Validator.number()
.precision(0)
.optional(),
ends_at: Validator.date().optional(),
is_disabled: Validator.boolean().optional(),
region_id: Validator.string().optional(),
metadata: Validator.object().optional(),
const validated = await validator(AdminPostGiftCardsGiftCardReq, req.body)
const giftCardService: GiftCardService = req.scope.resolve("giftCardService")
await giftCardService.update(id, validated)
const giftCard = await giftCardService.retrieve(id, {
select: defaultAdminGiftCardFields,
relations: defaultAdminGiftCardRelations,
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
res.status(200).json({ gift_card: giftCard })
}
try {
const giftCardService = req.scope.resolve("giftCardService")
export class AdminPostGiftCardsGiftCardReq {
@IsOptional()
@IsInt()
balance?: number
await giftCardService.update(id, value)
@IsOptional()
@IsBoolean()
is_disabled?: boolean
const giftCard = await giftCardService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
@IsOptional()
@IsDate()
@Type(() => Date)
ends_at?: Date
res.status(200).json({ gift_card: giftCard })
} catch (err) {
throw err
}
@IsOptional()
@IsString()
region_id?: string
@IsOptional()
metadata?: object
}
@@ -1,10 +1,13 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { IsNotEmpty, IsString } from "class-validator"
import NoteService from "../../../../services/note"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /notes
* operationId: "PostNotes"
* summary: "Creates a Note"
* description: "Creates a Note which can be associated with any resource as required."
* x-authenticated: true
* requestBody:
* content:
* application/json:
@@ -33,31 +36,32 @@ import { MedusaError, Validator } from "medusa-core-utils"
*
*/
export default async (req, res) => {
const schema = Validator.object().keys({
resource_id: Validator.string(),
resource_type: Validator.string(),
value: Validator.string(),
const validated = await validator(AdminPostNotesReq, req.body)
const userId: string = req.user.id || req.user.userId
const noteService: NoteService = req.scope.resolve("noteService")
const result = await noteService.create({
resource_id: validated.resource_id,
resource_type: validated.resource_type,
value: validated.value,
author_id: userId,
})
const userId = req.user.id || req.user.userId
res.status(200).json({ note: result })
}
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
export class AdminPostNotesReq {
@IsString()
@IsNotEmpty()
resource_id: string
try {
const noteService = req.scope.resolve("noteService")
@IsString()
@IsNotEmpty()
resource_type: string
const result = await noteService.create({
resource_id: value.resource_id,
resource_type: value.resource_type,
value: value.value,
author_id: userId,
})
res.status(200).json({ note: result })
} catch (err) {
throw err
}
@IsString()
@IsNotEmpty()
value: string
}
@@ -1,8 +1,11 @@
import NoteService from "../../../../services/note"
/**
* @oas [delete] /notes/{id}
* operationId: "DeleteNotesNote"
* summary: "Deletes a Note"
* description: "Deletes a Note."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Note to delete.
* tags:
@@ -24,12 +27,8 @@
export default async (req, res) => {
const { id } = req.params
try {
const noteService = req.scope.resolve("noteService")
await noteService.delete(id)
const noteService: NoteService = req.scope.resolve("noteService")
await noteService.delete(id)
res.status(200).json({ id, deleted: true })
} catch (err) {
throw err
}
res.status(200).json({ id, object: "note", deleted: true })
}
@@ -1,8 +1,11 @@
import NoteService from "../../../../services/note"
/**
* @oas [get] /notes/{id}
* operationId: "GetNoteNote"
* operationId: "GetNotesNote"
* summary: "Get Note"
* description: "Retrieves a single note using its id"
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the note to retrieve.
* tags:
@@ -20,12 +23,8 @@
export default async (req, res) => {
const { id } = req.params
try {
const noteService = req.scope.resolve("noteService")
const note = await noteService.retrieve(id, { relations: ["author"] })
const noteService: NoteService = req.scope.resolve("noteService")
const note = await noteService.retrieve(id, { relations: ["author"] })
res.status(200).json({ note })
} catch (err) {
throw err
}
res.status(200).json({ note })
}
@@ -1,9 +1,12 @@
import { Router } from "express"
import { Note } from "../../../.."
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
import middlewares from "../../../middlewares"
import "reflect-metadata"
const route = Router()
export default app => {
export default (app) => {
app.use("/notes", route)
route.get("/:id", middlewares.wrap(require("./get-note").default))
@@ -18,3 +21,18 @@ export default app => {
return app
}
export type AdminNotesRes = {
note: Note
}
export type AdminNotesDeleteRes = DeleteResponse
export type AdminNotesListRes = PaginatedResponse & {
notes: Note[]
}
export * from "./create-note"
export * from "./delete-note"
export * from "./get-note"
export * from "./list-notes"
export * from "./update-note"
@@ -1,42 +0,0 @@
/**
* @oas [get] /notes
* operationId: "GetNotes"
* summary: "List Notes"
* description: "Retrieves a list of notes"
* tags:
* - Note
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* notes:
* type: array
* items:
* $ref: "#/components/schemas/note"
*/
export default async (req, res) => {
try {
const limit = parseInt(req.query.limit) || 50
const offset = parseInt(req.query.offset) || 0
const selector = {}
if ("resource_id" in req.query) {
selector.resource_id = req.query.resource_id
}
const noteService = req.scope.resolve("noteService")
const notes = await noteService.list(selector, {
take: limit,
skip: offset,
relations: ["author"],
})
res.status(200).json({ notes })
} catch (err) {
throw err
}
}
@@ -0,0 +1,68 @@
import { IsNumber, IsOptional, IsString } from "class-validator"
import NoteService from "../../../../services/note"
import { validator } from "../../../../utils/validator"
import { selector } from "../../../../types/note"
import { Type } from "class-transformer"
/**
* @oas [get] /notes
* operationId: "GetNotes"
* summary: "List Notes"
* x-authenticated: true
* description: "Retrieves a list of notes"
* * parameters:
* - (path) limit= {number} The number of notes to get
* - (path) offset= {number} The offset at which to get notes
* - (path) resource_id= {string} The id which the notes belongs to
* tags:
* - Note
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* notes:
* type: array
* items:
* $ref: "#/components/schemas/note"
*/
export default async (req, res) => {
const validated = await validator(AdminGetNotesParams, req.query)
const selector: selector = {}
if (validated.resource_id) {
selector.resource_id = validated.resource_id
}
const noteService: NoteService = req.scope.resolve("noteService")
const notes = await noteService.list(selector, {
take: validated.limit,
skip: validated.offset,
relations: ["author"],
})
res.status(200).json({
notes,
count: notes.length,
offset: validated.offset,
limit: validated.limit,
})
}
export class AdminGetNotesParams {
@IsString()
@IsOptional()
resource_id?: string
@IsNumber()
@IsOptional()
@Type(() => Number)
limit = 50
@IsNumber()
@IsOptional()
@Type(() => Number)
offset = 0
}
@@ -1,9 +1,12 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { IsString } from "class-validator"
import NoteService from "../../../../services/note"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /notes/{id}
* operationId: "PostNotesNote"
* summary: "Updates a Note"
* x-authenticated: true
* description: "Updates a Note associated with some resource"
* parameters:
* - (path) id=* {string} The id of the Note to update
@@ -11,6 +14,8 @@ import { MedusaError, Validator } from "medusa-core-utils"
* content:
* application/json:
* schema:
* required:
* - value
* properties:
* value:
* type: string
@@ -31,21 +36,15 @@ import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
value: Validator.string(),
})
const validated = await validator(AdminPostNotesNoteReq, req.body)
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const noteService: NoteService = req.scope.resolve("noteService")
const note = await noteService.update(id, validated.value)
try {
const noteService = req.scope.resolve("noteService")
const result = await noteService.update(id, value.value)
res.status(200).json({ note })
}
res.status(200).json({ note: result })
} catch (err) {
throw err
}
export class AdminPostNotesNoteReq {
@IsString()
value: string
}
@@ -1,9 +1,11 @@
import { Router } from "express"
import { Notification } from "./../../../../"
import { PaginatedResponse } from "./../../../../types/common"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
export default (app) => {
app.use("/notifications", route)
/**
@@ -22,10 +24,10 @@ export default app => {
return app
}
export const defaultRelations = ["resends"]
export const allowedRelations = ["resends"]
export const defaultAdminNotificationsRelations = ["resends"]
export const allowedAdminNotificationsRelations = ["resends"]
export const defaultFields = [
export const defaultAdminNotificationsFields = [
"id",
"resource_type",
"resource_id",
@@ -36,7 +38,7 @@ export const defaultFields = [
"updated_at",
]
export const allowedFields = [
export const allowedAdminNotificationsFields = [
"id",
"resource_type",
"resource_id",
@@ -46,3 +48,11 @@ export const allowedFields = [
"created_at",
"updated_at",
]
export type AdminNotificationsListRes = {
notifications: Notification[]
}
export type AdminNotificationsRes = PaginatedResponse & {
notification: Notification
}
@@ -1,83 +0,0 @@
import _ from "lodash"
import { defaultRelations, defaultFields } from "./"
/**
* @oas [get] /notifications
* operationId: "GetNotifications"
* summary: "List Notifications"
* description: "Retrieves a list of Notifications."
* tags:
* - Notification
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* notifications:
* type: array
* items:
* $ref: "#/components/schemas/notification"
*/
export default async (req, res) => {
try {
const notificationService = req.scope.resolve("notificationService")
const limit = parseInt(req.query.limit) || 50
const offset = parseInt(req.query.offset) || 0
let selector = {}
let includeFields = []
if ("fields" in req.query) {
includeFields = req.query.fields.split(",")
}
let expandFields = []
if ("expand" in req.query) {
expandFields = req.query.expand.split(",")
}
if ("event_name" in req.query) {
const values = req.query.event_name.split(",")
selector.event_name = values.length > 1 ? values : values[0]
}
if ("resource_type" in req.query) {
const values = req.query.resource_type.split(",")
selector.resource_type = values.length > 1 ? values : values[0]
}
if ("resource_id" in req.query) {
const values = req.query.resource_id.split(",")
selector.resource_id = values.length > 1 ? values : values[0]
}
if ("to" in req.query) {
const values = req.query.to.split(",")
selector.to = values.length > 1 ? values : values[0]
}
if (!("include_resends" in req.query)) {
selector.parent_id = null
}
const listConfig = {
select: includeFields.length ? includeFields : defaultFields,
relations: expandFields.length ? expandFields : defaultRelations,
skip: offset,
take: limit,
order: { created_at: "DESC" },
}
const notifications = await notificationService.list(selector, listConfig)
const fields = [...listConfig.select, ...listConfig.relations]
const data = notifications.map(o => _.pick(o, fields))
res.json({ notifications: data, offset, limit })
} catch (error) {
throw error
}
}
@@ -0,0 +1,151 @@
import { Type } from "class-transformer"
import { IsBooleanString, IsInt, IsOptional, IsString } from "class-validator"
import { pick } from "lodash"
import { NotificationService } from "../../../../services"
import { validator } from "../../../../utils/validator"
import {
defaultAdminNotificationsFields,
defaultAdminNotificationsRelations,
} from "./"
/**
* @oas [get] /notifications
* operationId: "GetNotifications"
* summary: "List Notifications"
* description: "Retrieves a list of Notifications."
* x-authenticated: true
* parameters:
* - (query) offset=0 {integer} The number of notifications to skip before starting to collect the notifications set
* - (query) limit=50 {integer} The number of notifications to return
* - (query) fields {string} The fields to include in the result set
* - (query) expand {string} The fields to populate
* - (query) event_name {string}
* - (query) resource_type {string}
* - (query) resource_id {string}
* - (query) to {string}
* - (query) include_resends {boolean} Whether the result set should include resent notifications or not
* tags:
* - Notification
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* notifications:
* type: array
* items:
* $ref: "#/components/schemas/notification"
*/
export default async (req, res) => {
const notificationService: NotificationService = req.scope.resolve(
"notificationService"
)
const {
limit,
offset,
fields,
expand,
event_name,
resource_id,
resource_type,
to,
include_resends,
} = await validator(AdminGetNotificationsParams, req.query)
const selector: any = {}
let includeFields: string[] = []
if (fields) {
includeFields = fields.split(",")
}
let expandFields: string[] = []
if (expand) {
expandFields = expand.split(",")
}
if (event_name) {
const values = event_name.split(",")
selector.event_name = values.length > 1 ? values : values[0]
}
if (resource_type) {
const values = resource_type.split(",")
selector.resource_type = values.length > 1 ? values : values[0]
}
if (resource_id) {
const values = resource_id.split(",")
selector.resource_id = values.length > 1 ? values : values[0]
}
if (to) {
const values = to.split(",")
selector.to = values.length > 1 ? values : values[0]
}
if (!include_resends || include_resends === "false") {
selector.parent_id = null
}
const listConfig = {
select: includeFields.length
? includeFields
: defaultAdminNotificationsFields,
relations: expandFields.length
? expandFields
: defaultAdminNotificationsRelations,
skip: offset,
take: limit,
order: { created_at: "DESC" },
}
const notifications = await notificationService.list(selector, listConfig)
const resultFields = [...listConfig.select, ...listConfig.relations]
const data = notifications.map((o) => pick(o, resultFields))
res.json({ notifications: data })
}
export class AdminGetNotificationsParams {
@IsOptional()
@IsInt()
@Type(() => Number)
limit?: number = 50
@IsOptional()
@IsInt()
@Type(() => Number)
offset?: number = 0
@IsOptional()
@IsString()
fields?: string
@IsOptional()
@IsString()
expand?: string
@IsOptional()
@IsString()
event_name?: string
@IsOptional()
@IsString()
resource_type?: string
@IsOptional()
@IsString()
resource_id?: string
@IsOptional()
@IsString()
to?: string
@IsOptional()
@IsBooleanString()
include_resends?: string
}
@@ -1,55 +0,0 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
/**
* @oas [post] /notifications/{id}/resend
* operationId: "PostNotificationsNotificationResend"
* summary: "Resend Notification"
* description: "Resends a previously sent notifications, with the same data but optionally to a different address"
* parameters:
* - (path) id=* {string} The id of the Notification
* tags:
* - Notification
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* notification:
* $ref: "#/components/schemas/notification"
*/
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
to: Validator.string().optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const notificationService = req.scope.resolve("notificationService")
const config = {}
if (value.to) {
config.to = value.to
}
await notificationService.resend(id, config)
const notification = await notificationService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ notification })
} catch (error) {
throw error
}
}
@@ -0,0 +1,70 @@
import { IsString } from "class-validator"
import { IsOptional } from "class-validator"
import {
defaultAdminNotificationsFields,
defaultAdminNotificationsRelations,
} from "."
import { validator } from "../../../../utils/validator"
import { NotificationService } from "../../../../services"
/**
* @oas [post] /notifications/{id}/resend
* operationId: "PostNotificationsNotificationResend"
* summary: "Resend Notification"
* description: "Resends a previously sent notifications, with the same data but optionally to a different address"
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Notification
* requestBody:
* content:
* application/json:
* schema:
* properties:
* to:
* description: "The address or user identifier that the Notification was sent to"
* type: string
* tags:
* - Notification
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* notification:
* $ref: "#/components/schemas/notification"
*/
export default async (req, res) => {
const { id } = req.params
const validatedBody = await validator(
AdminPostNotificationsNotificationResendReq,
req.body
)
const notificationService: NotificationService = req.scope.resolve(
"notificationService"
)
const config: any = {}
if (validatedBody.to) {
config.to = validatedBody.to
}
await notificationService.resend(id, config)
const notification = await notificationService.retrieve(id, {
select: defaultAdminNotificationsFields,
relations: defaultAdminNotificationsRelations,
})
res.json({ notification })
}
export class AdminPostNotificationsNotificationResendReq {
@IsOptional()
@IsString()
to?: string
}
@@ -1,12 +1,20 @@
import _ from "lodash"
import { Validator, MedusaError } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
import {
IsInt,
IsNotEmpty,
IsObject,
IsOptional,
IsString,
} from "class-validator"
import { defaultAdminOrdersFields, defaultAdminOrdersRelations } from "."
import { OrderService } from "../../../../services"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /orders/{id}/shipping-methods
* operationId: "PostOrdersOrderShippingMethods"
* summary: "Add a Shipping Method"
* description: "Adds a Shipping Method to an Order. If another Shipping Method exists with the same Shipping Profile, the previous Shipping Method will be replaced."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Order.
* - (body) price=* {integer} The price (excluding VAT) that should be charged for the Shipping Method
@@ -27,37 +35,40 @@ import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
price: Validator.number()
.integer()
.integer()
.allow(0)
.required(),
option_id: Validator.string().required(),
data: Validator.object()
.optional()
.default({}),
const validated = await validator(
AdminPostOrdersOrderShippingMethodsReq,
req.body
)
const orderService: OrderService = req.scope.resolve("orderService")
await orderService.addShippingMethod(
id,
validated.option_id,
validated.data,
{
price: validated.price,
}
)
const order = await orderService.retrieve(id, {
select: defaultAdminOrdersFields,
relations: defaultAdminOrdersRelations,
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
res.status(200).json({ order })
}
try {
const orderService = req.scope.resolve("orderService")
export class AdminPostOrdersOrderShippingMethodsReq {
@IsInt()
@IsNotEmpty()
price: number
await orderService.addShippingMethod(id, value.option_id, value.data, {
price: value.price,
})
@IsString()
@IsNotEmpty()
option_id: string
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ order })
} catch (err) {
throw err
}
@IsObject()
@IsOptional()
data?: object = {}
}
@@ -1,17 +0,0 @@
export default async (req, res) => {
const { id } = req.params
try {
const orderService = req.scope.resolve("orderService")
await orderService.archive(id)
const order = await orderService.retrieve(id, {
relations: ["region", "customer", "swaps"],
})
res.json({ order })
} catch (error) {
throw error
}
}
@@ -0,0 +1,35 @@
import { OrderService } from "../../../../services"
/**
* @oas [post] /orders/{id}/archive
* operationId: "PostOrdersOrderArchive"
* summary: "Archive order"
* description: "Archives the order with the given id."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Order.
* tags:
* - Order
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* order:
* $ref: "#/components/schemas/order"
*/
export default async (req, res) => {
const { id } = req.params
const orderService: OrderService = req.scope.resolve("orderService")
await orderService.archive(id)
const order = await orderService.retrieve(id, {
relations: ["region", "customer", "swaps"],
})
res.json({ order })
}
@@ -1,51 +0,0 @@
import { MedusaError } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "."
/**
* @oas [post] /orders/{id}/claims/{claim_id}/cancel
* operationId: "PostOrdersClaimCancel"
* summary: "Cancels a Claim"
* description: "Cancels a Claim"
* parameters:
* - (path) id=* {string} The id of the Order.
* . (path) claim_id=* {string} The id of the Claim.
* tags:
* - Claim
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* order:
* $ref: "#/components/schemas/claim"
*/
export default async (req, res) => {
const { id, claim_id } = req.params
try {
const claimService = req.scope.resolve("claimService")
const orderService = req.scope.resolve("orderService")
const claim = await claimService.retrieve(claim_id)
if (claim.order_id !== id) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`no claim was found with the id: ${claim_id} related to order: ${id}`
)
}
await claimService.cancel(claim_id)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ order })
} catch (error) {
throw error
}
}
@@ -0,0 +1,49 @@
import { MedusaError } from "medusa-core-utils"
import { defaultAdminOrdersRelations, defaultAdminOrdersFields } from "."
import { ClaimService, OrderService } from "../../../../services"
/**
* @oas [post] /orders/{id}/claims/{claim_id}/cancel
* operationId: "PostOrdersClaimCancel"
* summary: "Cancels a Claim"
* description: "Cancels a Claim"
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Order.
* - (path) claim_id=* {string} The id of the Claim.
* tags:
* - Claim
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* order:
* $ref: "#/components/schemas/claim_order"
*/
export default async (req, res) => {
const { id, claim_id } = req.params
const claimService: ClaimService = req.scope.resolve("claimService")
const orderService: OrderService = req.scope.resolve("orderService")
const claim = await claimService.retrieve(claim_id)
if (claim.order_id !== id) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`no claim was found with the id: ${claim_id} related to order: ${id}`
)
}
await claimService.cancel(claim_id)
const order = await orderService.retrieve(id, {
select: defaultAdminOrdersFields,
relations: defaultAdminOrdersRelations,
})
res.json({ order })
}
@@ -1,61 +0,0 @@
import { MedusaError } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "."
/**
* @oas [post] orders//{id}/claims/{claim_id}/fulfillments/{fulfillment_id}/cancel
* operationId: "PostOrdersClaimFulfillmentsCancel"
* summary: "Cancels a fulfilmment related to a Claim"
* description: "Registers a Fulfillment as canceled."
* parameters:
* - (path) id=* {string} The id of the Order which the Claim relates to.
* - (path) claim_id=* {string} The id of the Claim which the Fulfillment relates to.
* - (path) fulfillment_id=* {string} The id of the Fulfillment.
* tags:
* - Fulfillment
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* fulfillment:
* $ref: "#/components/schemas/fulfillment"
*/
export default async (req, res) => {
const { id, claim_id, fulfillment_id } = req.params
try {
const fulfillmentService = req.scope.resolve("fulfillmentService")
const claimService = req.scope.resolve("claimService")
const orderService = req.scope.resolve("orderService")
const fulfillment = await fulfillmentService.retrieve(fulfillment_id)
if (fulfillment.claim_order_id !== claim_id) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`no fulfillment was found with the id: ${fulfillment_id} related to claim: ${claim_id}`
)
}
const claim = await claimService.retrieve(claim_id)
if (claim.order_id !== id) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`no claim was found with the id: ${claim_id} related to order: ${id}`
)
}
await claimService.cancelFulfillment(fulfillment_id)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ order })
} catch (error) {
throw error
}
}
@@ -0,0 +1,64 @@
import { MedusaError } from "medusa-core-utils"
import { defaultAdminOrdersRelations, defaultAdminOrdersFields } from "."
import {
ClaimService,
FulfillmentService,
OrderService,
} from "../../../../services"
/**
* @oas [post] /orders/{id}/claims/{claim_id}/fulfillments/{fulfillment_id}/cancel
* operationId: "PostOrdersClaimFulfillmentsCancel"
* summary: "Cancels a fulfilmment related to a Claim"
* description: "Registers a Fulfillment as canceled."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Order which the Claim relates to.
* - (path) claim_id=* {string} The id of the Claim which the Fulfillment relates to.
* - (path) fulfillment_id=* {string} The id of the Fulfillment.
* tags:
* - Fulfillment
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* fulfillment:
* $ref: "#/components/schemas/fulfillment"
*/
export default async (req, res) => {
const { id, claim_id, fulfillment_id } = req.params
const fulfillmentService: FulfillmentService =
req.scope.resolve("fulfillmentService")
const claimService: ClaimService = req.scope.resolve("claimService")
const orderService: OrderService = req.scope.resolve("orderService")
const fulfillment = await fulfillmentService.retrieve(fulfillment_id)
if (fulfillment.claim_order_id !== claim_id) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`no fulfillment was found with the id: ${fulfillment_id} related to claim: ${claim_id}`
)
}
const claim = await claimService.retrieve(claim_id)
if (claim.order_id !== id) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`no claim was found with the id: ${claim_id} related to order: ${id}`
)
}
await claimService.cancelFulfillment(fulfillment_id)
const order = await orderService.retrieve(id, {
select: defaultAdminOrdersFields,
relations: defaultAdminOrdersRelations,
})
res.json({ order })
}
@@ -1,62 +0,0 @@
import { MedusaError } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "."
/**
* @oas [post] /orders/{id}/swaps/{swap_id}/fulfillments/{fulfillment_id}/cancel
* operationId: "PostOrdersSwapFulfillmentsCancel"
* summary: "Cancels a fulfilmment related to a Swap"
* description: "Registers a Fulfillment as canceled."
* parameters:
* - (path) id=* {string} The id of the Order which the Swap relates to.
* - (path) swap_id=* {string} The id of the Swap which the Fulfillment relates to.
* - (path) fulfillment_id=* {string} The id of the Fulfillment.
* tags:
* - Fulfillment
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* fulfillment:
* $ref: "#/components/schemas/fulfillment"
*/
export default async (req, res) => {
const { id, swap_id, fulfillment_id } = req.params
try {
const fulfillmentService = req.scope.resolve("fulfillmentService")
const swapService = req.scope.resolve("swapService")
const orderService = req.scope.resolve("orderService")
const fulfillment = await fulfillmentService.retrieve(fulfillment_id)
if (fulfillment.swap_id !== swap_id) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`no fulfillment was found with the id: ${fulfillment_id} related to swap: ${id}`
)
}
const swap = await swapService.retrieve(swap_id)
if (swap.order_id !== id) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`no swap was found with the id: ${swap_id} related to order: ${id}`
)
}
await swapService.cancelFulfillment(fulfillment_id)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ order })
} catch (error) {
throw error
}
}
@@ -0,0 +1,65 @@
import { MedusaError } from "medusa-core-utils"
import { defaultAdminOrdersRelations, defaultAdminOrdersFields } from "."
import {
FulfillmentService,
OrderService,
SwapService,
} from "../../../../services"
/**
* @oas [post] /orders/{id}/swaps/{swap_id}/fulfillments/{fulfillment_id}/cancel
* operationId: "PostOrdersSwapFulfillmentsCancel"
* summary: "Cancels a fulfilmment related to a Swap"
* description: "Registers a Fulfillment as canceled."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Order which the Swap relates to.
* - (path) swap_id=* {string} The id of the Swap which the Fulfillment relates to.
* - (path) fulfillment_id=* {string} The id of the Fulfillment.
* tags:
* - Fulfillment
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* fulfillment:
* $ref: "#/components/schemas/fulfillment"
*/
export default async (req, res) => {
const { id, swap_id, fulfillment_id } = req.params
const swapService: SwapService = req.scope.resolve("swapService")
const orderService: OrderService = req.scope.resolve("orderService")
const fulfillmentService: FulfillmentService =
req.scope.resolve("fulfillmentService")
const fulfillment = await fulfillmentService.retrieve(fulfillment_id)
if (fulfillment.swap_id !== swap_id) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`no fulfillment was found with the id: ${fulfillment_id} related to swap: ${id}`
)
}
const swap = await swapService.retrieve(swap_id)
if (swap.order_id !== id) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`no swap was found with the id: ${swap_id} related to order: ${id}`
)
}
await swapService.cancelFulfillment(fulfillment_id)
const order = await orderService.retrieve(id, {
select: defaultAdminOrdersFields,
relations: defaultAdminOrdersRelations,
})
res.json({ order })
}
@@ -1,51 +0,0 @@
import { MedusaError } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "."
/**
* @oas [post] /orders/{id}/fulfillments/{fulfillment_id}/cancel
* operationId: "PostOrdersOrderFulfillmentsCancel"
* summary: "Cancels a fulfilmment"
* description: "Registers a Fulfillment as canceled."
* parameters:
* - (path) id=* {string} The id of the Order which the Fulfillment relates to.
* - (path) fulfillment_id=* {string} The id of the Fulfillment
* tags:
* - Fulfillment
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* fulfillment:
* $ref: "#/components/schemas/fulfillment"
*/
export default async (req, res) => {
const { id, fulfillment_id } = req.params
try {
const fulfillmentService = req.scope.resolve("fulfillmentService")
const orderService = req.scope.resolve("orderService")
const fulfillment = await fulfillmentService.retrieve(fulfillment_id)
if (fulfillment.order_id !== id) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`no fulfillment was found with the id: ${fulfillment_id} related to order: ${id}`
)
}
await orderService.cancelFulfillment(fulfillment_id)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ order })
} catch (error) {
throw error
}
}
@@ -0,0 +1,50 @@
import { MedusaError } from "medusa-core-utils"
import { defaultAdminOrdersRelations, defaultAdminOrdersFields } from "."
import { FulfillmentService, OrderService } from "../../../../services"
/**
* @oas [post] /orders/{id}/fulfillments/{fulfillment_id}/cancel
* operationId: "PostOrdersOrderFulfillmentsCancel"
* summary: "Cancels a fulfilmment"
* description: "Registers a Fulfillment as canceled."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Order which the Fulfillment relates to.
* - (path) fulfillment_id=* {string} The id of the Fulfillment
* tags:
* - Fulfillment
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* fulfillment:
* $ref: "#/components/schemas/fulfillment"
*/
export default async (req, res) => {
const { id, fulfillment_id } = req.params
const orderService: OrderService = req.scope.resolve("orderService")
const fulfillmentService: FulfillmentService =
req.scope.resolve("fulfillmentService")
const fulfillment = await fulfillmentService.retrieve(fulfillment_id)
if (fulfillment.order_id !== id) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`no fulfillment was found with the id: ${fulfillment_id} related to order: ${id}`
)
}
await orderService.cancelFulfillment(fulfillment_id)
const order = await orderService.retrieve(id, {
select: defaultAdminOrdersFields,
relations: defaultAdminOrdersRelations,
})
res.json({ order })
}
@@ -1,10 +1,12 @@
import { defaultFields, defaultRelations } from "."
import { defaultAdminOrdersFields, defaultAdminOrdersRelations } from "."
import { OrderService } from "../../../../services"
/**
* @oas [post] /orders/{id}/cancel
* operationId: "PostOrdersOrderCancel"
* summary: "Cancel an Order"
* description: "Registers an Order as canceled. This triggers a flow that will cancel any created Fulfillments and Payments, may fail if the Payment or Fulfillment Provider is unable to cancel the Payment/Fulfillment."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Order.
* tags:
@@ -22,17 +24,13 @@ import { defaultFields, defaultRelations } from "."
export default async (req, res) => {
const { id } = req.params
try {
const orderService = req.scope.resolve("orderService")
await orderService.cancel(id)
const orderService: OrderService = req.scope.resolve("orderService")
await orderService.cancel(id)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
const order = await orderService.retrieve(id, {
select: defaultAdminOrdersFields,
relations: defaultAdminOrdersRelations,
})
res.json({ order })
} catch (error) {
throw error
}
res.json({ order })
}
@@ -1,51 +0,0 @@
import { MedusaError } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "."
/**
* @oas [post] /orders/{id}/swaps/{swap_id}/cancel
* operationId: "PostOrdersSwapCancel"
* summary: "Cancels a Swap"
* description: "Cancels a Swap"
* parameters:
* - (path) id=* {string} The id of the Order.
* . (path) swap_id=* {string} The id of the Swap.
* tags:
* - Swap
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* order:
* $ref: "#/components/schemas/swap"
*/
export default async (req, res) => {
const { id, swap_id } = req.params
try {
const swapService = req.scope.resolve("swapService")
const orderService = req.scope.resolve("orderService")
const swap = await swapService.retrieve(swap_id)
if (swap.order_id !== id) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`no swap was found with the id: ${swap_id} related to order: ${id}`
)
}
await swapService.cancel(swap_id)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ order })
} catch (error) {
throw error
}
}
@@ -0,0 +1,49 @@
import { MedusaError } from "medusa-core-utils"
import { defaultAdminOrdersRelations, defaultAdminOrdersFields } from "."
import { OrderService, SwapService } from "../../../../services"
/**
* @oas [post] /orders/{id}/swaps/{swap_id}/cancel
* operationId: "PostOrdersSwapCancel"
* summary: "Cancels a Swap"
* description: "Cancels a Swap"
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Order.
* - (path) swap_id=* {string} The id of the Swap.
* tags:
* - Swap
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* order:
* $ref: "#/components/schemas/swap"
*/
export default async (req, res) => {
const { id, swap_id } = req.params
const swapService: SwapService = req.scope.resolve("swapService")
const orderService: OrderService = req.scope.resolve("orderService")
const swap = await swapService.retrieve(swap_id)
if (swap.order_id !== id) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`no swap was found with the id: ${swap_id} related to order: ${id}`
)
}
await swapService.cancel(swap_id)
const order = await orderService.retrieve(id, {
select: defaultAdminOrdersFields,
relations: defaultAdminOrdersRelations,
})
res.json({ order })
}

Some files were not shown because too many files have changed in this diff Show More