From 7cb8095ed4cdaf6b8be4835477a55e5bd07da1ec Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Thu, 28 Jul 2022 11:48:55 +0200 Subject: [PATCH] chore(medusa): Add transactions in mutating actions in store domain (#1858) --- .../api/routes/store/auth/create-session.ts | 12 +- .../api/routes/store/carts/calculate-taxes.ts | 109 +++++----- .../api/routes/store/carts/complete-cart.ts | 16 +- .../store/carts/create-payment-sessions.ts | 6 +- .../carts/decorate-line-items-with-totals.ts | 22 +- .../store/carts/delete-payment-session.ts | 7 +- .../store/carts/refresh-payment-session.ts | 6 +- .../routes/store/carts/set-payment-session.ts | 9 +- .../src/api/routes/store/carts/update-cart.ts | 19 +- .../routes/store/carts/update-line-item.ts | 2 +- .../store/carts/update-payment-session.ts | 8 +- .../routes/store/customers/create-address.ts | 11 +- .../routes/store/customers/create-customer.ts | 10 +- .../routes/store/customers/delete-address.ts | 8 +- .../store/customers/reset-password-token.ts | 8 +- .../routes/store/customers/reset-password.ts | 10 +- .../routes/store/customers/update-address.ts | 8 +- .../routes/store/customers/update-customer.ts | 8 +- .../api/routes/store/returns/create-return.ts | 184 ++++++++-------- .../src/api/routes/store/swaps/create-swap.ts | 198 +++++++++--------- packages/medusa/src/repositories/claim.ts | 2 +- .../src/services/__tests__/tax-provider.js | 9 + .../medusa/src/services/idempotency-key.js | 6 +- packages/medusa/src/services/swap.js | 10 +- packages/medusa/src/services/tax-provider.ts | 8 +- packages/medusa/src/services/totals.ts | 28 ++- 26 files changed, 437 insertions(+), 287 deletions(-) diff --git a/packages/medusa/src/api/routes/store/auth/create-session.ts b/packages/medusa/src/api/routes/store/auth/create-session.ts index 97ef3b8e89..b965e5d952 100644 --- a/packages/medusa/src/api/routes/store/auth/create-session.ts +++ b/packages/medusa/src/api/routes/store/auth/create-session.ts @@ -3,6 +3,7 @@ import jwt from "jsonwebtoken" import AuthService from "../../../../services/auth" import CustomerService from "../../../../services/customer" import { validator } from "../../../../utils/validator" +import { EntityManager } from "typeorm" /** * @oas [post] /auth @@ -28,10 +29,13 @@ export default async (req, res) => { const validated = await validator(StorePostAuthReq, req.body) const authService: AuthService = req.scope.resolve("authService") - const result = await authService.authenticateCustomer( - validated.email, - validated.password - ) + const manager: EntityManager = req.scope.resolve("manager") + const result = await manager.transaction(async (transactionManager) => { + return await authService + .withTransaction(transactionManager) + .authenticateCustomer(validated.email, validated.password) + }) + if (!result.success) { res.sendStatus(401) return diff --git a/packages/medusa/src/api/routes/store/carts/calculate-taxes.ts b/packages/medusa/src/api/routes/store/carts/calculate-taxes.ts index d13d5af8da..53fdf80093 100644 --- a/packages/medusa/src/api/routes/store/carts/calculate-taxes.ts +++ b/packages/medusa/src/api/routes/store/carts/calculate-taxes.ts @@ -31,17 +31,22 @@ export default async (req, res) => { const idempotencyKeyService: IdempotencyKeyService = req.scope.resolve( "idempotencyKeyService" ) + const manager: EntityManager = req.scope.resolve("manager") const headerKey = req.get("Idempotency-Key") || "" - let idempotencyKey: IdempotencyKey + let idempotencyKey!: IdempotencyKey try { - idempotencyKey = await idempotencyKeyService.initializeRequest( - headerKey, - req.method, - req.params, - req.path - ) + await manager.transaction(async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .initializeRequest( + headerKey, + req.method, + req.params, + req.path + ) + }) } catch (error) { console.log(error) res.status(409).send("Failed to create idempotency key") @@ -59,42 +64,46 @@ export default async (req, res) => { while (inProgress) { switch (idempotencyKey.recovery_point) { case "started": { - const { key, error } = await idempotencyKeyService.workStage( - idempotencyKey.idempotency_key, - async (manager: EntityManager) => { - const cart = await cartService.withTransaction(manager).retrieve( - id, - { - relations: ["items", "items.adjustments"], - select: [ - "total", - "subtotal", - "tax_total", - "discount_total", - "shipping_total", - "gift_card_total", - ], - }, - { force_taxes: true } + await manager.transaction(async (transactionManager) => { + const { key, error } = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage( + idempotencyKey.idempotency_key, + async (manager: EntityManager) => { + const cart = await cartService.withTransaction(manager).retrieve( + id, + { + relations: ["items", "items.adjustments"], + select: [ + "total", + "subtotal", + "tax_total", + "discount_total", + "shipping_total", + "gift_card_total", + ], + }, + { force_taxes: true } + ) + + const data = await decorateLineItemsWithTotals(cart, req, { + force_taxes: true, + }) + + return { + response_code: 200, + response_body: { cart: data }, + } + } ) - const data = await decorateLineItemsWithTotals(cart, req, { - force_taxes: true, - }) - - return { - response_code: 200, - response_body: { cart: data }, - } + if (error) { + inProgress = false + err = error + } else { + idempotencyKey = key } - ) - - if (error) { - inProgress = false - err = error - } else { - idempotencyKey = key - } + }) break } @@ -104,14 +113,18 @@ export default async (req, res) => { } default: - idempotencyKey = await idempotencyKeyService.update( - idempotencyKey.idempotency_key, - { - recovery_point: "finished", - response_code: 500, - response_body: { message: "Unknown recovery point" }, - } - ) + await manager.transaction(async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .update( + idempotencyKey.idempotency_key, + { + recovery_point: "finished", + response_code: 500, + response_body: { message: "Unknown recovery point" }, + } + ) + }) break } } diff --git a/packages/medusa/src/api/routes/store/carts/complete-cart.ts b/packages/medusa/src/api/routes/store/carts/complete-cart.ts index bcf018aca6..c23588515a 100644 --- a/packages/medusa/src/api/routes/store/carts/complete-cart.ts +++ b/packages/medusa/src/api/routes/store/carts/complete-cart.ts @@ -1,6 +1,7 @@ import { ICartCompletionStrategy } from "../../../../interfaces" import { IdempotencyKeyService } from "../../../../services" import { IdempotencyKey } from "../../../../models/idempotency-key" +import { EntityManager } from "typeorm"; /** * @oas [post] /carts/{id}/complete @@ -50,12 +51,15 @@ export default async (req, res) => { let idempotencyKey: IdempotencyKey try { - idempotencyKey = await idempotencyKeyService.initializeRequest( - headerKey, - req.method, - req.params, - req.path - ) + const manager: EntityManager = req.scope.resolve("manager") + idempotencyKey = await manager.transaction(async (transactionManager) => { + return await idempotencyKeyService.withTransaction(transactionManager).initializeRequest( + headerKey, + req.method, + req.params, + req.path + ) + }) } catch (error) { console.log(error) res.status(409).send("Failed to create idempotency key") diff --git a/packages/medusa/src/api/routes/store/carts/create-payment-sessions.ts b/packages/medusa/src/api/routes/store/carts/create-payment-sessions.ts index 3c61a72791..15d5a8dbac 100644 --- a/packages/medusa/src/api/routes/store/carts/create-payment-sessions.ts +++ b/packages/medusa/src/api/routes/store/carts/create-payment-sessions.ts @@ -1,6 +1,7 @@ import { defaultStoreCartFields, defaultStoreCartRelations } from "." import { CartService } from "../../../../services" import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals" +import { EntityManager } from "typeorm"; /** * @oas [post] /carts/{id}/payment-sessions @@ -26,7 +27,10 @@ export default async (req, res) => { const cartService: CartService = req.scope.resolve("cartService") - await cartService.setPaymentSessions(id) + const manager: EntityManager = req.scope.resolve("manager") + await manager.transaction(async (transactionManager) => { + return await cartService.withTransaction(transactionManager).setPaymentSessions(id) + }) const cart = await cartService.retrieve(id, { select: defaultStoreCartFields, diff --git a/packages/medusa/src/api/routes/store/carts/decorate-line-items-with-totals.ts b/packages/medusa/src/api/routes/store/carts/decorate-line-items-with-totals.ts index 167e3ed36e..8707770db8 100644 --- a/packages/medusa/src/api/routes/store/carts/decorate-line-items-with-totals.ts +++ b/packages/medusa/src/api/routes/store/carts/decorate-line-items-with-totals.ts @@ -1,6 +1,7 @@ import { Request } from "express" import { TotalsService } from "../../../../services" import { Cart, LineItem } from "../../../../models" +import { EntityManager } from "typeorm"; export const decorateLineItemsWithTotals = async ( cart: Cart, @@ -10,15 +11,20 @@ export const decorateLineItemsWithTotals = async ( const totalsService: TotalsService = req.scope.resolve("totalsService") if (cart.items && cart.region) { - const items = await Promise.all( - cart.items.map(async (item: LineItem) => { - const itemTotals = await totalsService.getLineItemTotals(item, cart, { - include_tax: options.force_taxes || cart.region.automatic_taxes, - }) + const manager: EntityManager = req.scope.resolve("manager") + const items = await manager.transaction(async (transactionManager) => { + return await Promise.all( + cart.items.map(async (item: LineItem) => { + const itemTotals = await totalsService + .withTransaction(transactionManager) + .getLineItemTotals(item, cart, { + include_tax: options.force_taxes || cart.region.automatic_taxes, + }) - return Object.assign(item, itemTotals) - }) - ) + return Object.assign(item, itemTotals) + }) + ) + }) return Object.assign(cart, { items }) } diff --git a/packages/medusa/src/api/routes/store/carts/delete-payment-session.ts b/packages/medusa/src/api/routes/store/carts/delete-payment-session.ts index a14b5ce1d5..55ea6af601 100644 --- a/packages/medusa/src/api/routes/store/carts/delete-payment-session.ts +++ b/packages/medusa/src/api/routes/store/carts/delete-payment-session.ts @@ -1,6 +1,7 @@ import { defaultStoreCartFields, defaultStoreCartRelations } from "." import { CartService } from "../../../../services" import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals" +import { EntityManager } from "typeorm"; /** * @oas [delete] /carts/{id}/payment-sessions/{provider_id} @@ -27,7 +28,11 @@ export default async (req, res) => { const cartService: CartService = req.scope.resolve("cartService") - await cartService.deletePaymentSession(id, provider_id) + const manager: EntityManager = req.scope.resolve("manager") + await manager.transaction(async (transactionManager) => { + return await cartService.withTransaction(transactionManager).deletePaymentSession(id, provider_id) + }) + const cart = await cartService.retrieve(id, { select: defaultStoreCartFields, relations: defaultStoreCartRelations, diff --git a/packages/medusa/src/api/routes/store/carts/refresh-payment-session.ts b/packages/medusa/src/api/routes/store/carts/refresh-payment-session.ts index d120072f5b..131c066b65 100644 --- a/packages/medusa/src/api/routes/store/carts/refresh-payment-session.ts +++ b/packages/medusa/src/api/routes/store/carts/refresh-payment-session.ts @@ -1,5 +1,6 @@ import { CartService } from "../../../../services" import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals" +import { EntityManager } from "typeorm"; /** * @oas [post] /carts/{id}/payment-sessions/{provider_id}/refresh @@ -26,7 +27,10 @@ export default async (req, res) => { const cartService: CartService = req.scope.resolve("cartService") - await cartService.refreshPaymentSession(id, provider_id) + const manager: EntityManager = req.scope.resolve("manager") + await manager.transaction(async (transactionManager) => { + return await cartService.withTransaction(transactionManager).refreshPaymentSession(id, provider_id) + }) const cart = await cartService.retrieve(id, { select: [ "subtotal", diff --git a/packages/medusa/src/api/routes/store/carts/set-payment-session.ts b/packages/medusa/src/api/routes/store/carts/set-payment-session.ts index b3c1b1d2ee..4d31d49b07 100644 --- a/packages/medusa/src/api/routes/store/carts/set-payment-session.ts +++ b/packages/medusa/src/api/routes/store/carts/set-payment-session.ts @@ -3,6 +3,7 @@ import { defaultStoreCartFields, defaultStoreCartRelations } from "." import { CartService } from "../../../../services" import { validator } from "../../../../utils/validator" import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals" +import { EntityManager } from "typeorm"; /** * @oas [post] /carts/{id}/payment-session @@ -34,8 +35,12 @@ export default async (req, res) => { const cartService: CartService = req.scope.resolve("cartService") - let cart = await cartService.setPaymentSession(id, validated.provider_id) - cart = await cartService.retrieve(id, { + const manager: EntityManager = req.scope.resolve("manager") + await manager.transaction(async (transactionManager) => { + return await cartService.withTransaction(transactionManager).setPaymentSession(id, validated.provider_id) + }) + + const cart = await cartService.retrieve(id, { select: defaultStoreCartFields, relations: defaultStoreCartRelations, }) diff --git a/packages/medusa/src/api/routes/store/carts/update-cart.ts b/packages/medusa/src/api/routes/store/carts/update-cart.ts index 8803681436..7a14cb902e 100644 --- a/packages/medusa/src/api/routes/store/carts/update-cart.ts +++ b/packages/medusa/src/api/routes/store/carts/update-cart.ts @@ -11,6 +11,7 @@ import { CartService } from "../../../../services" import { AddressPayload } from "../../../../types/common" import { IsType } from "../../../../utils/validators/is-type" import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals" +import { EntityManager } from "typeorm"; import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"; import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"; @@ -89,16 +90,20 @@ export default async (req, res) => { const validated = req.validatedBody as StorePostCartsCartReq const cartService: CartService = req.scope.resolve("cartService") - await cartService.update(id, validated) + const manager: EntityManager = req.scope.resolve("manager") - const updated = await cartService.retrieve(id, { - relations: ["payment_sessions", "shipping_methods"], + await manager.transaction(async (transactionManager) => { + await cartService.withTransaction(transactionManager).update(id, validated) + + const updated = await cartService.withTransaction(transactionManager).retrieve(id, { + relations: ["payment_sessions", "shipping_methods"], + }) + + if (updated.payment_sessions?.length && !validated.region_id) { + await cartService.withTransaction(transactionManager).setPaymentSessions(id) + } }) - if (updated.payment_sessions?.length && !validated.region_id) { - await cartService.setPaymentSessions(id) - } - const cart = await cartService.retrieve(id, { select: defaultStoreCartFields, relations: defaultStoreCartRelations, diff --git a/packages/medusa/src/api/routes/store/carts/update-line-item.ts b/packages/medusa/src/api/routes/store/carts/update-line-item.ts index 7e107f5aa8..df81403938 100644 --- a/packages/medusa/src/api/routes/store/carts/update-line-item.ts +++ b/packages/medusa/src/api/routes/store/carts/update-line-item.ts @@ -43,7 +43,7 @@ export default async (req, res) => { if (validated.quantity === 0) { await cartService.withTransaction(m).removeLineItem(id, line_id) } else { - const cart = await cartService.retrieve(id, { relations: ["items"] }) + const cart = await cartService.withTransaction(m).retrieve(id, { relations: ["items"] }) const existing = cart.items.find((i) => i.id === line_id) if (!existing) { diff --git a/packages/medusa/src/api/routes/store/carts/update-payment-session.ts b/packages/medusa/src/api/routes/store/carts/update-payment-session.ts index 378876e87c..279e6512c9 100644 --- a/packages/medusa/src/api/routes/store/carts/update-payment-session.ts +++ b/packages/medusa/src/api/routes/store/carts/update-payment-session.ts @@ -3,6 +3,7 @@ import { defaultStoreCartFields, defaultStoreCartRelations } from "." import { CartService } from "../../../../services" import { validator } from "../../../../utils/validator" import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals" +import { EntityManager } from "typeorm"; /** * @oas [post] /carts/{id}/payment-sessions/{provider_id} @@ -35,8 +36,11 @@ export default async (req, res) => { const cartService: CartService = req.scope.resolve("cartService") - await cartService.setPaymentSession(id, provider_id) - await cartService.updatePaymentSession(id, validated.data) + const manager: EntityManager = req.scope.resolve("manager") + await manager.transaction(async (transactionManager) => { + await cartService.withTransaction(transactionManager).setPaymentSession(id, provider_id) + await cartService.withTransaction(transactionManager).updatePaymentSession(id, validated.data) + }) const cart = await cartService.retrieve(id, { select: defaultStoreCartFields, diff --git a/packages/medusa/src/api/routes/store/customers/create-address.ts b/packages/medusa/src/api/routes/store/customers/create-address.ts index 7ed20adc13..3b7a7d78b6 100644 --- a/packages/medusa/src/api/routes/store/customers/create-address.ts +++ b/packages/medusa/src/api/routes/store/customers/create-address.ts @@ -4,6 +4,7 @@ import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." import CustomerService from "../../../../services/customer" import { AddressCreatePayload } from "../../../../types/common" import { validator } from "../../../../utils/validator" +import { EntityManager } from "typeorm" /** * @oas [post] /customers/me/addresses @@ -44,8 +45,14 @@ export default async (req, res) => { const customerService: CustomerService = req.scope.resolve("customerService") - let customer = await customerService.addAddress(id, validated.address) - customer = await customerService.retrieve(id, { + const manager: EntityManager = req.scope.resolve("manager") + await manager.transaction(async (transactionManager) => { + return await customerService + .withTransaction(transactionManager) + .addAddress(id, validated.address) + }) + + const customer = await customerService.retrieve(id, { relations: defaultStoreCustomersRelations, select: defaultStoreCustomersFields, }) diff --git a/packages/medusa/src/api/routes/store/customers/create-customer.ts b/packages/medusa/src/api/routes/store/customers/create-customer.ts index 4ef56fa9bf..3280532f89 100644 --- a/packages/medusa/src/api/routes/store/customers/create-customer.ts +++ b/packages/medusa/src/api/routes/store/customers/create-customer.ts @@ -4,6 +4,7 @@ import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." import { Customer } from "../../../.." import CustomerService from "../../../../services/customer" import { validator } from "../../../../utils/validator" +import { EntityManager } from "typeorm" /** * @oas [post] /customers @@ -32,7 +33,14 @@ export default async (req, res) => { const validated = await validator(StorePostCustomersReq, req.body) const customerService: CustomerService = req.scope.resolve("customerService") - let customer: Customer = await customerService.create(validated) + const manager: EntityManager = req.scope.resolve("manager") + let customer: Customer = await manager.transaction( + async (transactionManager) => { + return await customerService + .withTransaction(transactionManager) + .create(validated) + } + ) // Add JWT to cookie const { diff --git a/packages/medusa/src/api/routes/store/customers/delete-address.ts b/packages/medusa/src/api/routes/store/customers/delete-address.ts index 7385d8f2a9..7083e0d516 100644 --- a/packages/medusa/src/api/routes/store/customers/delete-address.ts +++ b/packages/medusa/src/api/routes/store/customers/delete-address.ts @@ -1,5 +1,6 @@ import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." import CustomerService from "../../../../services/customer" +import { EntityManager } from "typeorm" /** * @oas [delete] /customers/me/addresses/{address_id} @@ -28,7 +29,12 @@ export default async (req, res) => { const customerService: CustomerService = req.scope.resolve("customerService") - await customerService.removeAddress(id, address_id) + const manager: EntityManager = req.scope.resolve("manager") + await manager.transaction(async (transactionManager) => { + return await customerService + .withTransaction(transactionManager) + .removeAddress(id, address_id) + }) const customer = await customerService.retrieve(id, { relations: defaultStoreCustomersRelations, diff --git a/packages/medusa/src/api/routes/store/customers/reset-password-token.ts b/packages/medusa/src/api/routes/store/customers/reset-password-token.ts index 1e0fcfc3ca..f692ff32d3 100644 --- a/packages/medusa/src/api/routes/store/customers/reset-password-token.ts +++ b/packages/medusa/src/api/routes/store/customers/reset-password-token.ts @@ -1,6 +1,7 @@ import { IsEmail } from "class-validator" import CustomerService from "../../../../services/customer" import { validator } from "../../../../utils/validator" +import { EntityManager } from "typeorm" /** * @oas [post] /customers/password-token @@ -28,7 +29,12 @@ export default async (req, res) => { const customer = await customerService.retrieveByEmail(validated.email) // Will generate a token and send it to the customer via an email provider - await customerService.generateResetPasswordToken(customer.id) + const manager: EntityManager = req.scope.resolve("manager") + await manager.transaction(async (transactionManager) => { + return await customerService + .withTransaction(transactionManager) + .generateResetPasswordToken(customer.id) + }) res.sendStatus(204) } diff --git a/packages/medusa/src/api/routes/store/customers/reset-password.ts b/packages/medusa/src/api/routes/store/customers/reset-password.ts index c47c167515..6520983704 100644 --- a/packages/medusa/src/api/routes/store/customers/reset-password.ts +++ b/packages/medusa/src/api/routes/store/customers/reset-password.ts @@ -2,6 +2,7 @@ import { IsEmail, IsString } from "class-validator" import jwt, { JwtPayload } from "jsonwebtoken" import CustomerService from "../../../../services/customer" import { validator } from "../../../../utils/validator" +import { EntityManager } from "typeorm" /** * @oas [post] /customers/reset-password @@ -44,8 +45,13 @@ export default async (req, res) => { return } - await customerService.update(customer.id, { - password: validated.password, + const manager: EntityManager = req.scope.resolve("manager") + await manager.transaction(async (transactionManager) => { + return await customerService + .withTransaction(transactionManager) + .update(customer.id, { + password: validated.password, + }) }) customer = await customerService.retrieve(customer.id) diff --git a/packages/medusa/src/api/routes/store/customers/update-address.ts b/packages/medusa/src/api/routes/store/customers/update-address.ts index 2e1716b37e..fd07cb2936 100644 --- a/packages/medusa/src/api/routes/store/customers/update-address.ts +++ b/packages/medusa/src/api/routes/store/customers/update-address.ts @@ -2,6 +2,7 @@ import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." import CustomerService from "../../../../services/customer" import { AddressPayload } from "../../../../types/common" import { validator } from "../../../../utils/validator" +import { EntityManager } from "typeorm" /** * @oas [post] /customers/me/addresses/{address_id} @@ -45,7 +46,12 @@ export default async (req, res) => { "customerService" ) as CustomerService - await customerService.updateAddress(id, address_id, validated) + const manager: EntityManager = req.scope.resolve("manager") + await manager.transaction(async (transactionManager) => { + return await customerService + .withTransaction(transactionManager) + .updateAddress(id, address_id, validated) + }) const customer = await customerService.retrieve(id, { relations: defaultStoreCustomersRelations, diff --git a/packages/medusa/src/api/routes/store/customers/update-customer.ts b/packages/medusa/src/api/routes/store/customers/update-customer.ts index e223c4982d..598200e8e4 100644 --- a/packages/medusa/src/api/routes/store/customers/update-customer.ts +++ b/packages/medusa/src/api/routes/store/customers/update-customer.ts @@ -4,6 +4,7 @@ import CustomerService from "../../../../services/customer" import { AddressPayload } from "../../../../types/common" import { IsType } from "../../../../utils/validators/is-type" import { validator } from "../../../../utils/validator" +import { EntityManager } from "typeorm" /** * @oas [post] /customers/me @@ -56,7 +57,12 @@ export default async (req, res) => { const validated = await validator(StorePostCustomersCustomerReq, req.body) const customerService: CustomerService = req.scope.resolve("customerService") - await customerService.update(id, validated) + const manager: EntityManager = req.scope.resolve("manager") + await manager.transaction(async (transactionManager) => { + return await customerService + .withTransaction(transactionManager) + .update(id, validated) + }) const customer = await customerService.retrieve(id, { relations: defaultStoreCustomersRelations, diff --git a/packages/medusa/src/api/routes/store/returns/create-return.ts b/packages/medusa/src/api/routes/store/returns/create-return.ts index 006067212f..f280a82361 100644 --- a/packages/medusa/src/api/routes/store/returns/create-return.ts +++ b/packages/medusa/src/api/routes/store/returns/create-return.ts @@ -14,6 +14,7 @@ import IdempotencyKeyService from "../../../../services/idempotency-key" import OrderService from "../../../../services/order" import ReturnService from "../../../../services/return" import { validator } from "../../../../utils/validator" +import { EntityManager } from "typeorm"; /** * @oas [post] /returns @@ -72,17 +73,20 @@ export default async (req, res) => { const idempotencyKeyService: IdempotencyKeyService = req.scope.resolve( "idempotencyKeyService" ) + const manager: EntityManager = req.scope.resolve("manager") const headerKey = req.get("Idempotency-Key") || "" let idempotencyKey try { - idempotencyKey = await idempotencyKeyService.initializeRequest( - headerKey, - req.method, - req.params, - req.path - ) + await manager.transaction(async (transactionManager) => { + idempotencyKey = await idempotencyKeyService.withTransaction(transactionManager).initializeRequest( + headerKey, + req.method, + req.params, + req.path + ) + }) } catch (error) { res.status(409).send("Failed to create idempotency key") return @@ -102,91 +106,99 @@ export default async (req, res) => { while (inProgress) { switch (idempotencyKey.recovery_point) { case "started": { - const { key, error } = await idempotencyKeyService.workStage( - idempotencyKey.idempotency_key, - async (manager) => { - const order = await orderService - .withTransaction(manager) - .retrieve(returnDto.order_id, { - select: ["refunded_total", "total"], - relations: ["items"], - }) + await manager.transaction(async (transactionManager) => { + const { key, error } = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage( + idempotencyKey.idempotency_key, + async (manager) => { + const order = await orderService + .withTransaction(manager) + .retrieve(returnDto.order_id, { + select: ["refunded_total", "total"], + relations: ["items"], + }) - const returnObj: any = { - order_id: returnDto.order_id, - idempotency_key: idempotencyKey.idempotency_key, - items: returnDto.items, - } + const returnObj: any = { + order_id: returnDto.order_id, + idempotency_key: idempotencyKey.idempotency_key, + items: returnDto.items, + } - if (returnDto.return_shipping) { - returnObj.shipping_method = returnDto.return_shipping - } + if (returnDto.return_shipping) { + returnObj.shipping_method = returnDto.return_shipping + } - const createdReturn = await returnService - .withTransaction(manager) - .create(returnObj) + const createdReturn = await returnService + .withTransaction(manager) + .create(returnObj) - if (returnDto.return_shipping) { - await returnService - .withTransaction(manager) - .fulfill(createdReturn.id) - } + if (returnDto.return_shipping) { + await returnService + .withTransaction(manager) + .fulfill(createdReturn.id) + } - await eventBus - .withTransaction(manager) - .emit("order.return_requested", { - id: returnDto.order_id, - return_id: createdReturn.id, - }) + await eventBus + .withTransaction(manager) + .emit("order.return_requested", { + id: returnDto.order_id, + return_id: createdReturn.id, + }) - return { - recovery_point: "return_requested", - } + return { + recovery_point: "return_requested", + } + } + ) + + if (error) { + inProgress = false + err = error + } else { + idempotencyKey = key } - ) - - if (error) { - inProgress = false - err = error - } else { - idempotencyKey = key - } + }) break } case "return_requested": { - const { key, error } = await idempotencyKeyService.workStage( - idempotencyKey.idempotency_key, - async (manager) => { - let ret = await returnService.withTransaction(manager).list( - { - idempotency_key: idempotencyKey.idempotency_key, - }, - { - relations: ["items", "items.reason"], + await manager.transaction(async (transactionManager) => { + const { key, error } = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage( + idempotencyKey.idempotency_key, + async (manager) => { + let ret = await returnService.withTransaction(manager).list( + { + idempotency_key: idempotencyKey.idempotency_key, + }, + { + relations: ["items", "items.reason"], + } + ) + if (!ret.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Return not found` + ) + } + ret = ret[0] + + return { + response_code: 200, + response_body: { return: ret }, + } } ) - if (!ret.length) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - `Return not found` - ) - } - ret = ret[0] - return { - response_code: 200, - response_body: { return: ret }, - } + if (error) { + inProgress = false + err = error + } else { + idempotencyKey = key } - ) - - if (error) { - inProgress = false - err = error - } else { - idempotencyKey = key - } + }) break } @@ -196,14 +208,18 @@ export default async (req, res) => { } default: - idempotencyKey = await idempotencyKeyService.update( - idempotencyKey.idempotency_key, - { - recovery_point: "finished", - response_code: 500, - response_body: { message: "Unknown recovery point" }, - } - ) + await manager.transaction(async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .update( + idempotencyKey.idempotency_key, + { + recovery_point: "finished", + response_code: 500, + response_body: { message: "Unknown recovery point" }, + } + ) + }) break } } diff --git a/packages/medusa/src/api/routes/store/swaps/create-swap.ts b/packages/medusa/src/api/routes/store/swaps/create-swap.ts index 255081aaab..366b1ae794 100644 --- a/packages/medusa/src/api/routes/store/swaps/create-swap.ts +++ b/packages/medusa/src/api/routes/store/swaps/create-swap.ts @@ -93,17 +93,17 @@ export default async (req, res) => { const idempotencyKeyService: IdempotencyKeyService = req.scope.resolve( "idempotencyKeyService" ) + const manager: EntityManager = req.scope.resolve("manager") const headerKey = req.get("Idempotency-Key") || "" let idempotencyKey try { - idempotencyKey = await idempotencyKeyService.initializeRequest( - headerKey, - req.method, - req.params, - req.path - ) + await manager.transaction(async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .initializeRequest(headerKey, req.method, req.params, req.path) + }) } catch (error) { res.status(409).send("Failed to create idempotency key") return @@ -122,101 +122,108 @@ export default async (req, res) => { while (inProgress) { switch (idempotencyKey.recovery_point) { case "started": { - const { key, error } = await idempotencyKeyService.workStage( - idempotencyKey.idempotency_key, - async (manager) => { - const order = await orderService - .withTransaction(manager) - .retrieve(swapDto.order_id, { - select: ["refunded_total", "total"], - relations: [ - "items", - "items.tax_lines", - "swaps", - "swaps.additional_items", - "swaps.additional_items.tax_lines", - ], - }) + await manager.transaction(async (transactionManager) => { + const { key, error } = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { + const order = await orderService + .withTransaction(manager) + .retrieve(swapDto.order_id, { + select: ["refunded_total", "total"], + relations: [ + "items", + "items.tax_lines", + "swaps", + "swaps.additional_items", + "swaps.additional_items.tax_lines", + ], + }) - let returnShipping - if (swapDto.return_shipping_option) { - returnShipping = { - option_id: swapDto.return_shipping_option, - } - } - - const swap = await swapService - .withTransaction(manager) - .create( - order, - swapDto.return_items, - swapDto.additional_items, - returnShipping, - { - idempotency_key: idempotencyKey.idempotency_key, - no_notification: true, + let returnShipping + if (swapDto.return_shipping_option) { + returnShipping = { + option_id: swapDto.return_shipping_option, } - ) + } - await swapService.withTransaction(manager).createCart(swap.id) - const returnOrder = await returnService - .withTransaction(manager) - .retrieveBySwap(swap.id) + const swap = await swapService + .withTransaction(manager) + .create( + order, + swapDto.return_items, + swapDto.additional_items, + returnShipping, + { + idempotency_key: idempotencyKey.idempotency_key, + no_notification: true, + } + ) - await returnService.withTransaction(manager).fulfill(returnOrder.id) + await swapService.withTransaction(manager).createCart(swap.id) + const returnOrder = await returnService + .withTransaction(manager) + .retrieveBySwap(swap.id) - return { - recovery_point: "swap_created", - } + await returnService + .withTransaction(manager) + .fulfill(returnOrder.id) + + return { + recovery_point: "swap_created", + } + }) + + if (error) { + inProgress = false + err = error + } else { + idempotencyKey = key } - ) - - if (error) { - inProgress = false - err = error - } else { - idempotencyKey = key - } + }) break } case "swap_created": { - const { key, error } = await idempotencyKeyService.workStage( - idempotencyKey.idempotency_key, - async (transactionManager: EntityManager) => { - const swaps = await swapService - .withTransaction(transactionManager) - .list({ - idempotency_key: idempotencyKey.idempotency_key, - }) + await manager.transaction(async (transactionManager) => { + const { key, error } = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage( + idempotencyKey.idempotency_key, + async (transactionManager: EntityManager) => { + const swaps = await swapService + .withTransaction(transactionManager) + .list({ + idempotency_key: idempotencyKey.idempotency_key, + }) - if (!swaps.length) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - "Swap not found" - ) - } + if (!swaps.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "Swap not found" + ) + } - const swap = await swapService - .withTransaction(transactionManager) - .retrieve(swaps[0].id, { - select: defaultStoreSwapFields, - relations: defaultStoreSwapRelations, - }) + const swap = await swapService + .withTransaction(transactionManager) + .retrieve(swaps[0].id, { + select: defaultStoreSwapFields, + relations: defaultStoreSwapRelations, + }) - return { - response_code: 200, - response_body: { swap }, - } + return { + response_code: 200, + response_body: { swap }, + } + } + ) + + if (error) { + inProgress = false + err = error + } else { + idempotencyKey = key } - ) - - if (error) { - inProgress = false - err = error - } else { - idempotencyKey = key - } + }) break } @@ -226,14 +233,15 @@ export default async (req, res) => { } default: - idempotencyKey = await idempotencyKeyService.update( - idempotencyKey.idempotency_key, - { - recovery_point: "finished", - response_code: 500, - response_body: { message: "Unknown recovery point" }, - } - ) + await manager.transaction(async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .update(idempotencyKey.idempotency_key, { + recovery_point: "finished", + response_code: 500, + response_body: { message: "Unknown recovery point" }, + }) + }) break } } diff --git a/packages/medusa/src/repositories/claim.ts b/packages/medusa/src/repositories/claim.ts index 863d387ff8..3d3621aa40 100644 --- a/packages/medusa/src/repositories/claim.ts +++ b/packages/medusa/src/repositories/claim.ts @@ -1,4 +1,4 @@ -import { EntityRepository, Repository } from "typeorm" + import { EntityRepository, Repository } from "typeorm" import { ClaimOrder } from "../models/claim-order" @EntityRepository(ClaimOrder) diff --git a/packages/medusa/src/services/__tests__/tax-provider.js b/packages/medusa/src/services/__tests__/tax-provider.js index 3b097528c7..960da08888 100644 --- a/packages/medusa/src/services/__tests__/tax-provider.js +++ b/packages/medusa/src/services/__tests__/tax-provider.js @@ -117,6 +117,9 @@ describe("TaxProviderService", () => { test("success", async () => { container.taxRateService = { + withTransaction: function () { + return this + }, listByProduct: jest.fn(() => Promise.resolve([])), } @@ -155,6 +158,9 @@ describe("TaxProviderService", () => { test("success - without product rates", async () => { container.taxRateService = { + withTransaction: function () { + return this + }, listByProduct: jest.fn(() => Promise.resolve([])), } @@ -193,6 +199,9 @@ describe("TaxProviderService", () => { test("success - with product rates", async () => { container.taxRateService = { + withTransaction: function () { + return this + }, listByProduct: jest.fn(() => Promise.resolve([ { diff --git a/packages/medusa/src/services/idempotency-key.js b/packages/medusa/src/services/idempotency-key.js index 3bdfecd774..175124e3aa 100644 --- a/packages/medusa/src/services/idempotency-key.js +++ b/packages/medusa/src/services/idempotency-key.js @@ -1,12 +1,12 @@ import { MedusaError } from "medusa-core-utils" -import { BaseService } from "medusa-interfaces" import { v4 } from "uuid" +import { TransactionBaseService } from "../interfaces" const KEY_LOCKED_TIMEOUT = 1000 -class IdempotencyKeyService extends BaseService { +class IdempotencyKeyService extends TransactionBaseService { constructor({ manager, idempotencyKeyRepository }) { - super() + super({ manager, idempotencyKeyRepository }) /** @private @constant {EntityManager} */ this.manager_ = manager diff --git a/packages/medusa/src/services/swap.js b/packages/medusa/src/services/swap.js index 04760792d0..8d54d95622 100644 --- a/packages/medusa/src/services/swap.js +++ b/packages/medusa/src/services/swap.js @@ -197,10 +197,12 @@ class SwapService extends BaseService { } if (cartRelations || cartSelects) { - const cart = await this.cartService_.retrieve(swap.cart_id, { - select: cartSelects, - relations: cartRelations, - }) + const cart = await this.cartService_ + .withTransaction(this.manager_) + .retrieve(swap.cart_id, { + select: cartSelects, + relations: cartRelations, + }) swap.cart = cart } diff --git a/packages/medusa/src/services/tax-provider.ts b/packages/medusa/src/services/tax-provider.ts index c8a50d7fc0..4936a382e5 100644 --- a/packages/medusa/src/services/tax-provider.ts +++ b/packages/medusa/src/services/tax-provider.ts @@ -385,9 +385,11 @@ class TaxProviderService extends BaseService { } let toReturn: TaxServiceRate[] = [] - const productRates = await this.taxRateService_.listByProduct(productId, { - region_id: region.id, - }) + const productRates = await this.taxRateService_ + .withTransaction(this.manager_) + .listByProduct(productId, { + region_id: region.id, + }) if (productRates.length > 0) { toReturn = productRates.map((pr) => { diff --git a/packages/medusa/src/services/totals.ts b/packages/medusa/src/services/totals.ts index 7f1838b0f5..e0b3d83065 100644 --- a/packages/medusa/src/services/totals.ts +++ b/packages/medusa/src/services/totals.ts @@ -1,6 +1,10 @@ import { MedusaError } from "medusa-core-utils" import { BaseService } from "medusa-interfaces" -import { ITaxCalculationStrategy, TaxCalculationContext } from "../interfaces" +import { + ITaxCalculationStrategy, + TaxCalculationContext, + TransactionBaseService, +} from "../interfaces" import { Cart } from "../models/cart" import { Discount } from "../models/discount" import { DiscountRuleType } from "../models/discount-rule" @@ -18,6 +22,7 @@ import { SubtotalOptions, } from "../types/totals" import TaxProviderService from "./tax-provider" +import { EntityManager } from "typeorm" type ShippingMethodTotals = { price: number @@ -60,6 +65,7 @@ type GetLineItemTotalOptions = { type TotalsServiceProps = { taxProviderService: TaxProviderService taxCalculationStrategy: ITaxCalculationStrategy + manager: EntityManager } type GetTotalsOptions = { @@ -83,18 +89,27 @@ type CalculationContextOptions = { * A service that calculates total and subtotals for orders, carts etc.. * @implements {BaseService} */ -class TotalsService extends BaseService { +class TotalsService extends TransactionBaseService { + protected manager_: EntityManager + protected transactionManager_: EntityManager + private taxProviderService_: TaxProviderService private taxCalculationStrategy_: ITaxCalculationStrategy constructor({ taxProviderService, taxCalculationStrategy, + manager, }: TotalsServiceProps) { - super() + super({ + taxProviderService, + taxCalculationStrategy, + manager, + }) this.taxProviderService_ = taxProviderService this.taxCalculationStrategy_ = taxCalculationStrategy + this.manager_ = manager } /** @@ -757,10 +772,9 @@ class TotalsService extends BaseService { } taxLines = lineItem.tax_lines } else { - const orderLines = await this.taxProviderService_.getTaxLines( - cartOrOrder.items, - calculationContext - ) + const orderLines = await this.taxProviderService_ + .withTransaction(this.manager_) + .getTaxLines(cartOrOrder.items, calculationContext) taxLines = orderLines.filter((ol) => { if ("item_id" in ol) {