diff --git a/backend/src/api/public/v1/members/project-affiliations/patchProjectAffiliation.ts b/backend/src/api/public/v1/members/project-affiliations/patchProjectAffiliation.ts index dae531bff1..ce7682eb7f 100644 --- a/backend/src/api/public/v1/members/project-affiliations/patchProjectAffiliation.ts +++ b/backend/src/api/public/v1/members/project-affiliations/patchProjectAffiliation.ts @@ -2,10 +2,11 @@ import type { Request, Response } from 'express' import { z } from 'zod' import { captureApiChange, memberEditAffiliationsAction } from '@crowd/audit-logs' -import { NotFoundError } from '@crowd/common' +import { BadRequestError, NotFoundError } from '@crowd/common' import { signalMemberUpdate } from '@crowd/common_services' import { MemberField, + fetchManyOrganizationAffiliationPolicies, fetchMemberProjectSegments, fetchMemberSegmentAffiliationsForProject, findMaintainerRoles, @@ -62,6 +63,17 @@ export async function patchProjectAffiliation(req: Request, res: Response): Prom throw new NotFoundError('Project not found') } + if (affiliations.length > 0) { + const policies = await fetchManyOrganizationAffiliationPolicies( + qx, + affiliations.map((a) => a.organizationId), + ) + + if ([...policies.values()].some((isBlocked) => isBlocked)) { + throw new BadRequestError('This organization does not allow affiliations') + } + } + const existingAffiliations = await fetchMemberSegmentAffiliationsForProject( qx, memberId, diff --git a/backend/src/services/member/memberAffiliationsService.ts b/backend/src/services/member/memberAffiliationsService.ts index 573c93dad4..966682a49e 100644 --- a/backend/src/services/member/memberAffiliationsService.ts +++ b/backend/src/services/member/memberAffiliationsService.ts @@ -5,6 +5,7 @@ import { Error400, dateIntersects, groupBy } from '@crowd/common' import { signalMemberUpdate } from '@crowd/common_services' import { changeMemberOrganizationAffiliationOverrides, + fetchManyOrganizationAffiliationPolicies, fetchMemberOrganizations, findMemberAffiliationOverrides, } from '@crowd/data-access-layer' @@ -67,6 +68,18 @@ export default class MemberAffiliationsService extends LoggerBase { memberId: string, data: Partial[], ): Promise { + if (data?.length > 0) { + const qx = SequelizeRepository.getQueryExecutor(this.options) + const organizationIds = data + .map((a) => a.organizationId) + .filter((id): id is string => Boolean(id)) + const policies = await fetchManyOrganizationAffiliationPolicies(qx, organizationIds) + + if ([...policies.values()].some((isBlocked) => isBlocked)) { + throw new Error400(this.options.language, 'This organization does not allow affiliations') + } + } + return MemberAffiliationsRepository.upsertMultiple(memberId, data, this.options) }