Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor and improve memberOrganizationService transaction handling #2849

Merged
merged 1 commit into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

197 changes: 147 additions & 50 deletions backend/src/services/member/memberOrganizationsService.ts
Original file line number Diff line number Diff line change
@@ -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<IOrganization, 'id' | 'displayName' | 'logo'>

export default class MemberOrganizationsService extends LoggerBase {
options: IServiceOptions

Expand All @@ -17,70 +29,155 @@ export default class MemberOrganizationsService extends LoggerBase {
}

// Member organization list
async list(memberId: string): Promise<IOrganization[]> {
return MemberOrganizationsRepository.list(memberId, this.options)
}
async list(memberId: string): Promise<IRenderFriendlyMemberOrganization[]> {
const qx = SequelizeRepository.getQueryExecutor(this.options)

// Member organization creation
async create(memberId: string, data: Partial<IMemberOrganization>): Promise<IOrganization[]> {
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<string, IOrganizationSummary> = organizations.reduce(
(obj: Record<string, IOrganizationSummary>, 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<IMemberOrganization>,
): Promise<IRenderFriendlyMemberOrganization[]> {
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
async update(
id: string,
memberId: string,
data: Partial<IMemberOrganization>,
): Promise<IOrganization[]> {
const memberOrganizations = await MemberOrganizationsRepository.update(
id,
memberId,
data,
this.options,
)
await MemberAffiliationService.startAffiliationRecalculation(
memberId,
[data.organizationId],
this.options,
)
return memberOrganizations
): Promise<IRenderFriendlyMemberOrganization[]> {
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<IOrganization[]> {
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<IRenderFriendlyMemberOrganization[]> {
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
}
}
}
Loading