From babdcf236e0ada788bfecb0bc20c4354dfe14ace Mon Sep 17 00:00:00 2001 From: Yeganathan S Date: Tue, 18 Feb 2025 15:50:32 +0530 Subject: [PATCH] refactor and improve memberOrganizationService transaction handling --- .../member/memberOrganizationsRepository.ts | 122 ----------- .../member/memberOrganizationsService.ts | 197 +++++++++++++----- 2 files changed, 147 insertions(+), 172 deletions(-) delete mode 100644 backend/src/database/repositories/member/memberOrganizationsRepository.ts diff --git a/backend/src/database/repositories/member/memberOrganizationsRepository.ts b/backend/src/database/repositories/member/memberOrganizationsRepository.ts deleted file mode 100644 index 885dcc8abd..0000000000 --- a/backend/src/database/repositories/member/memberOrganizationsRepository.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { findOverrides as findMemberOrganizationAffiliationOverrides } from '@crowd/data-access-layer/src/member_organization_affiliation_overrides' -import { - cleanSoftDeletedMemberOrganization, - createMemberOrganization, - deleteMemberOrganization, - fetchMemberOrganizations, - updateMemberOrganization, -} from '@crowd/data-access-layer/src/members' -import { OrganizationField, queryOrgs } from '@crowd/data-access-layer/src/orgs' -import { IMemberOrganization, IOrganization, IRenderFriendlyMemberOrganization } from '@crowd/types' - -import { IRepositoryOptions } from '../IRepositoryOptions' -import SequelizeRepository from '../sequelizeRepository' - -type IOrganizationSummary = Pick - -class MemberOrganizationsRepository { - static async list( - memberId: string, - options: IRepositoryOptions, - ): Promise { - const qx = SequelizeRepository.getQueryExecutor(options) - - // Fetch member organizations - const memberOrganizations: IMemberOrganization[] = await fetchMemberOrganizations(qx, memberId) - - if (memberOrganizations.length === 0) { - return [] - } - - // Parse unique organization ids - const orgIds: string[] = [...new Set(memberOrganizations.map((mo) => mo.organizationId))] - - // Fetch organizations - let organizations: IOrganizationSummary[] = [] - if (orgIds.length) { - organizations = await queryOrgs(qx, { - filter: { - [OrganizationField.ID]: { - in: orgIds, - }, - }, - fields: [OrganizationField.ID, OrganizationField.DISPLAY_NAME, OrganizationField.LOGO], - }) - } - - // Fetch affiliation overrides - const affiliationOverrides = await findMemberOrganizationAffiliationOverrides( - qx, - memberId, - memberOrganizations.map((mo) => mo.id), - ) - - // Create mapping by id to speed up the processing - const orgByid: Record = organizations.reduce( - (obj: Record, org) => ({ - ...obj, - [org.id]: org, - }), - {}, - ) - - // Format the results - return memberOrganizations.map((mo) => ({ - ...(orgByid[mo.organizationId] || {}), - id: mo.organizationId, - memberOrganizations: { - ...mo, - affiliationOverride: affiliationOverrides.find((ao) => ao.memberOrganizationId === mo.id), - }, - })) - } - - static async create( - memberId: string, - data: Partial, - options: IRepositoryOptions, - ): Promise { - const qx = SequelizeRepository.getQueryExecutor(options) - - // Hard delete any existing soft-deleted member organization to prevent conflict errors - // when adding a similar entry. - await cleanSoftDeletedMemberOrganization(qx, memberId, data.organizationId, data) - - // Create member organization - await createMemberOrganization(qx, memberId, data) - - // List all member organizations - return this.list(memberId, options) - } - - static async update( - id: string, - memberId: string, - data: Partial, - options: IRepositoryOptions, - ): Promise { - const qx = SequelizeRepository.getQueryExecutor(options) - - // Hard delete any existing soft-deleted member organization to prevent conflict errors - // when updating a similar entry. - await cleanSoftDeletedMemberOrganization(qx, memberId, data.organizationId, data) - - // Update member organization - await updateMemberOrganization(qx, memberId, id, data) - - // List all member organizations - return this.list(memberId, options) - } - - static async delete(id: string, memberId: string, options: IRepositoryOptions) { - const qx = SequelizeRepository.getQueryExecutor(options) - - // Delete organization - await deleteMemberOrganization(qx, memberId, id) - - // List all member organizations - return this.list(memberId, options) - } -} - -export default MemberOrganizationsRepository diff --git a/backend/src/services/member/memberOrganizationsService.ts b/backend/src/services/member/memberOrganizationsService.ts index d741a97856..141b34ba55 100644 --- a/backend/src/services/member/memberOrganizationsService.ts +++ b/backend/src/services/member/memberOrganizationsService.ts @@ -1,13 +1,25 @@ /* eslint-disable no-continue */ import { Error404 } from '@crowd/common' +import { + OrganizationField, + cleanSoftDeletedMemberOrganization, + createMemberOrganization, + deleteMemberOrganization, + fetchMemberOrganizations, + queryOrgs, + updateMemberOrganization, +} from '@crowd/data-access-layer' +import { findOverrides as findMemberOrganizationAffiliationOverrides } from '@crowd/data-access-layer/src/member_organization_affiliation_overrides' import { LoggerBase } from '@crowd/logging' -import { IMemberOrganization, IOrganization } from '@crowd/types' +import { IMemberOrganization, IOrganization, IRenderFriendlyMemberOrganization } from '@crowd/types' -import MemberOrganizationsRepository from '@/database/repositories/member/memberOrganizationsRepository' +import SequelizeRepository from '@/database/repositories/sequelizeRepository' import { IServiceOptions } from '../IServiceOptions' import MemberAffiliationService from '../memberAffiliationService' +type IOrganizationSummary = Pick + export default class MemberOrganizationsService extends LoggerBase { options: IServiceOptions @@ -17,23 +29,92 @@ export default class MemberOrganizationsService extends LoggerBase { } // Member organization list - async list(memberId: string): Promise { - return MemberOrganizationsRepository.list(memberId, this.options) - } + async list(memberId: string): Promise { + const qx = SequelizeRepository.getQueryExecutor(this.options) - // Member organization creation - async create(memberId: string, data: Partial): Promise { - const memberOrganizations = await MemberOrganizationsRepository.create( + // Fetch member organizations + const memberOrganizations: IMemberOrganization[] = await fetchMemberOrganizations(qx, memberId) + + if (memberOrganizations.length === 0) { + return [] + } + + // Parse unique organization ids + const orgIds: string[] = [...new Set(memberOrganizations.map((mo) => mo.organizationId))] + + // Fetch organizations + let organizations: IOrganizationSummary[] = [] + if (orgIds.length) { + organizations = await queryOrgs(qx, { + filter: { + [OrganizationField.ID]: { + in: orgIds, + }, + }, + fields: [OrganizationField.ID, OrganizationField.DISPLAY_NAME, OrganizationField.LOGO], + }) + } + + // Fetch affiliation overrides + const affiliationOverrides = await findMemberOrganizationAffiliationOverrides( + qx, memberId, - data, - this.options, + memberOrganizations.map((mo) => mo.id), ) - await MemberAffiliationService.startAffiliationRecalculation( - memberId, - [data.organizationId], - this.options, + + // Create mapping by id to speed up the processing + const orgByid: Record = organizations.reduce( + (obj: Record, org) => ({ + ...obj, + [org.id]: org, + }), + {}, ) - return memberOrganizations + + // Format the results + return memberOrganizations.map((mo) => ({ + ...(orgByid[mo.organizationId] || {}), + id: mo.organizationId, + memberOrganizations: { + ...mo, + affiliationOverride: affiliationOverrides.find((ao) => ao.memberOrganizationId === mo.id), + }, + })) + } + + // Member organization creation + async create( + memberId: string, + data: Partial, + ): Promise { + const transaction = await SequelizeRepository.createTransaction(this.options) + const repositoryOptions = { ...this.options, transaction } + + try { + const qx = SequelizeRepository.getQueryExecutor(repositoryOptions) + + // Clean up any soft-deleted entries + await cleanSoftDeletedMemberOrganization(qx, memberId, data.organizationId, data) + + // Create new member organization + await createMemberOrganization(qx, memberId, data) + + // Start affiliation recalculation within the same transaction + await MemberAffiliationService.startAffiliationRecalculation( + memberId, + [data.organizationId], + repositoryOptions, + ) + + // Fetch updated list + const result = await this.list(memberId) + + await SequelizeRepository.commitTransaction(transaction) + return result + } catch (error) { + await SequelizeRepository.rollbackTransaction(transaction) + throw error + } } // Update member organization @@ -41,46 +122,62 @@ export default class MemberOrganizationsService extends LoggerBase { id: string, memberId: string, data: Partial, - ): Promise { - const memberOrganizations = await MemberOrganizationsRepository.update( - id, - memberId, - data, - this.options, - ) - await MemberAffiliationService.startAffiliationRecalculation( - memberId, - [data.organizationId], - this.options, - ) - return memberOrganizations + ): Promise { + const transaction = await SequelizeRepository.createTransaction(this.options) + const repositoryOptions = { ...this.options, transaction } + + try { + const qx = SequelizeRepository.getQueryExecutor(repositoryOptions) + + await cleanSoftDeletedMemberOrganization(qx, memberId, data.organizationId, data) + await updateMemberOrganization(qx, memberId, id, data) + + await MemberAffiliationService.startAffiliationRecalculation( + memberId, + [data.organizationId], + repositoryOptions, + ) + + const result = await this.list(memberId) + + await SequelizeRepository.commitTransaction(transaction) + return result + } catch (error) { + await SequelizeRepository.rollbackTransaction(transaction) + throw error + } } // Delete member organization - async delete(id: string, memberId: string): Promise { - const existingMemberOrganizations = await MemberOrganizationsRepository.list( - memberId, - this.options, - ) - const memberOrganizationToBeDeleted = existingMemberOrganizations.find( - (mo) => mo.memberOrganizations.id === id, - ) - if (!memberOrganizationToBeDeleted) { - throw new Error404(`Member organization with id ${id} not found!`) - } + async delete(id: string, memberId: string): Promise { + const transaction = await SequelizeRepository.createTransaction(this.options) + const repositoryOptions = { ...this.options, transaction } - const remainingMemberOrganizations = await MemberOrganizationsRepository.delete( - id, - memberId, - this.options, - ) + try { + const qx = SequelizeRepository.getQueryExecutor(repositoryOptions) - await MemberAffiliationService.startAffiliationRecalculation( - memberId, - [memberOrganizationToBeDeleted.memberOrganizations.organizationId], - this.options, - ) + const existingMemberOrganizations = await fetchMemberOrganizations(qx, memberId) + const memberOrganizationToBeDeleted = existingMemberOrganizations.find((mo) => mo.id === id) - return remainingMemberOrganizations + if (!memberOrganizationToBeDeleted) { + throw new Error404(`Member organization with id ${id} not found!`) + } + + await deleteMemberOrganization(qx, memberId, id) + + await MemberAffiliationService.startAffiliationRecalculation( + memberId, + [memberOrganizationToBeDeleted.organizationId], + repositoryOptions, + ) + + const result = await this.list(memberId) + + await SequelizeRepository.commitTransaction(transaction) + return result + } catch (error) { + await SequelizeRepository.rollbackTransaction(transaction) + throw error + } } }