@multitenantkit/domain 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/organization-memberships/index.d.ts +3 -0
- package/dist/organization-memberships/index.d.ts.map +1 -0
- package/dist/organization-memberships/index.js +3 -0
- package/dist/organization-memberships/index.js.map +1 -0
- package/dist/organization-memberships/use-cases/accept-organization-invitation/AcceptOrganizationInvitation.d.ts +26 -0
- package/dist/organization-memberships/use-cases/accept-organization-invitation/AcceptOrganizationInvitation.d.ts.map +1 -0
- package/dist/organization-memberships/use-cases/accept-organization-invitation/AcceptOrganizationInvitation.js +85 -0
- package/dist/organization-memberships/use-cases/accept-organization-invitation/AcceptOrganizationInvitation.js.map +1 -0
- package/dist/organization-memberships/use-cases/add-organization-member/AddOrganizationMember.d.ts +19 -0
- package/dist/organization-memberships/use-cases/add-organization-member/AddOrganizationMember.d.ts.map +1 -0
- package/dist/organization-memberships/use-cases/add-organization-member/AddOrganizationMember.js +126 -0
- package/dist/organization-memberships/use-cases/add-organization-member/AddOrganizationMember.js.map +1 -0
- package/dist/organization-memberships/use-cases/index.d.ts +6 -0
- package/dist/organization-memberships/use-cases/index.d.ts.map +1 -0
- package/dist/organization-memberships/use-cases/index.js +8 -0
- package/dist/organization-memberships/use-cases/index.js.map +1 -0
- package/dist/organization-memberships/use-cases/leave-organization/LeaveOrganization.d.ts +15 -0
- package/dist/organization-memberships/use-cases/leave-organization/LeaveOrganization.d.ts.map +1 -0
- package/dist/organization-memberships/use-cases/leave-organization/LeaveOrganization.js +64 -0
- package/dist/organization-memberships/use-cases/leave-organization/LeaveOrganization.js.map +1 -0
- package/dist/organization-memberships/use-cases/remove-organization-member/RemoveOrganizationMember.d.ts +16 -0
- package/dist/organization-memberships/use-cases/remove-organization-member/RemoveOrganizationMember.d.ts.map +1 -0
- package/dist/organization-memberships/use-cases/remove-organization-member/RemoveOrganizationMember.js +87 -0
- package/dist/organization-memberships/use-cases/remove-organization-member/RemoveOrganizationMember.js.map +1 -0
- package/dist/organization-memberships/use-cases/update-organization-member-role/UpdateOrganizationMemberRole.d.ts +16 -0
- package/dist/organization-memberships/use-cases/update-organization-member-role/UpdateOrganizationMemberRole.d.ts.map +1 -0
- package/dist/organization-memberships/use-cases/update-organization-member-role/UpdateOrganizationMemberRole.js +85 -0
- package/dist/organization-memberships/use-cases/update-organization-member-role/UpdateOrganizationMemberRole.js.map +1 -0
- package/dist/organizations/index.d.ts +2 -0
- package/dist/organizations/index.d.ts.map +1 -0
- package/dist/organizations/index.js +3 -0
- package/dist/organizations/index.js.map +1 -0
- package/dist/organizations/use-cases/archive-organization/ArchiveOrganization.d.ts +38 -0
- package/dist/organizations/use-cases/archive-organization/ArchiveOrganization.d.ts.map +1 -0
- package/dist/organizations/use-cases/archive-organization/ArchiveOrganization.js +117 -0
- package/dist/organizations/use-cases/archive-organization/ArchiveOrganization.js.map +1 -0
- package/dist/organizations/use-cases/create-organization/CreateOrganization.d.ts +19 -0
- package/dist/organizations/use-cases/create-organization/CreateOrganization.d.ts.map +1 -0
- package/dist/organizations/use-cases/create-organization/CreateOrganization.js +96 -0
- package/dist/organizations/use-cases/create-organization/CreateOrganization.js.map +1 -0
- package/dist/organizations/use-cases/delete-organization/DeleteOrganization.d.ts +34 -0
- package/dist/organizations/use-cases/delete-organization/DeleteOrganization.d.ts.map +1 -0
- package/dist/organizations/use-cases/delete-organization/DeleteOrganization.js +103 -0
- package/dist/organizations/use-cases/delete-organization/DeleteOrganization.js.map +1 -0
- package/dist/organizations/use-cases/get-organization/GetOrganization.d.ts +18 -0
- package/dist/organizations/use-cases/get-organization/GetOrganization.d.ts.map +1 -0
- package/dist/organizations/use-cases/get-organization/GetOrganization.js +51 -0
- package/dist/organizations/use-cases/get-organization/GetOrganization.js.map +1 -0
- package/dist/organizations/use-cases/index.d.ts +9 -0
- package/dist/organizations/use-cases/index.d.ts.map +1 -0
- package/dist/organizations/use-cases/index.js +11 -0
- package/dist/organizations/use-cases/index.js.map +1 -0
- package/dist/organizations/use-cases/list-organization-members/ListOrganizationMembers.d.ts +46 -0
- package/dist/organizations/use-cases/list-organization-members/ListOrganizationMembers.d.ts.map +1 -0
- package/dist/organizations/use-cases/list-organization-members/ListOrganizationMembers.js +130 -0
- package/dist/organizations/use-cases/list-organization-members/ListOrganizationMembers.js.map +1 -0
- package/dist/organizations/use-cases/restore-organization/RestoreOrganization.d.ts +30 -0
- package/dist/organizations/use-cases/restore-organization/RestoreOrganization.d.ts.map +1 -0
- package/dist/organizations/use-cases/restore-organization/RestoreOrganization.js +98 -0
- package/dist/organizations/use-cases/restore-organization/RestoreOrganization.js.map +1 -0
- package/dist/organizations/use-cases/transfer-organization-ownership/TransferOrganizationOwnership.d.ts +30 -0
- package/dist/organizations/use-cases/transfer-organization-ownership/TransferOrganizationOwnership.d.ts.map +1 -0
- package/dist/organizations/use-cases/transfer-organization-ownership/TransferOrganizationOwnership.js +139 -0
- package/dist/organizations/use-cases/transfer-organization-ownership/TransferOrganizationOwnership.js.map +1 -0
- package/dist/organizations/use-cases/update-organization/UpdateOrganization.d.ts +22 -0
- package/dist/organizations/use-cases/update-organization/UpdateOrganization.d.ts.map +1 -0
- package/dist/organizations/use-cases/update-organization/UpdateOrganization.js +100 -0
- package/dist/organizations/use-cases/update-organization/UpdateOrganization.js.map +1 -0
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +4 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/result/Result.d.ts +30 -0
- package/dist/shared/result/Result.d.ts.map +1 -0
- package/dist/shared/result/Result.js +69 -0
- package/dist/shared/result/Result.js.map +1 -0
- package/dist/shared/result/index.d.ts +4 -0
- package/dist/shared/result/index.d.ts.map +1 -0
- package/dist/shared/result/index.js +2 -0
- package/dist/shared/result/index.js.map +1 -0
- package/dist/shared/use-case/BaseUseCase.d.ts +164 -0
- package/dist/shared/use-case/BaseUseCase.d.ts.map +1 -0
- package/dist/shared/use-case/BaseUseCase.js +366 -0
- package/dist/shared/use-case/BaseUseCase.js.map +1 -0
- package/dist/shared/use-case/UseCaseHelpers.d.ts +43 -0
- package/dist/shared/use-case/UseCaseHelpers.d.ts.map +1 -0
- package/dist/shared/use-case/UseCaseHelpers.js +56 -0
- package/dist/shared/use-case/UseCaseHelpers.js.map +1 -0
- package/dist/shared/use-case/index.d.ts +3 -0
- package/dist/shared/use-case/index.d.ts.map +1 -0
- package/dist/shared/use-case/index.js +4 -0
- package/dist/shared/use-case/index.js.map +1 -0
- package/dist/users/index.d.ts +2 -0
- package/dist/users/index.d.ts.map +1 -0
- package/dist/users/index.js +3 -0
- package/dist/users/index.js.map +1 -0
- package/dist/users/use-cases/create-user/CreateUser.d.ts +21 -0
- package/dist/users/use-cases/create-user/CreateUser.d.ts.map +1 -0
- package/dist/users/use-cases/create-user/CreateUser.js +81 -0
- package/dist/users/use-cases/create-user/CreateUser.js.map +1 -0
- package/dist/users/use-cases/delete-user/DeleteUser.d.ts +35 -0
- package/dist/users/use-cases/delete-user/DeleteUser.d.ts.map +1 -0
- package/dist/users/use-cases/delete-user/DeleteUser.js +120 -0
- package/dist/users/use-cases/delete-user/DeleteUser.js.map +1 -0
- package/dist/users/use-cases/get-user/GetUser.d.ts +18 -0
- package/dist/users/use-cases/get-user/GetUser.d.ts.map +1 -0
- package/dist/users/use-cases/get-user/GetUser.js +29 -0
- package/dist/users/use-cases/get-user/GetUser.js.map +1 -0
- package/dist/users/use-cases/index.d.ts +6 -0
- package/dist/users/use-cases/index.d.ts.map +1 -0
- package/dist/users/use-cases/index.js +8 -0
- package/dist/users/use-cases/index.js.map +1 -0
- package/dist/users/use-cases/list-user-organizations/ListUserOrganizations.d.ts +20 -0
- package/dist/users/use-cases/list-user-organizations/ListUserOrganizations.d.ts.map +1 -0
- package/dist/users/use-cases/list-user-organizations/ListUserOrganizations.js +68 -0
- package/dist/users/use-cases/list-user-organizations/ListUserOrganizations.js.map +1 -0
- package/dist/users/use-cases/update-user/UpdateUser.d.ts +19 -0
- package/dist/users/use-cases/update-user/UpdateUser.d.ts.map +1 -0
- package/dist/users/use-cases/update-user/UpdateUser.js +73 -0
- package/dist/users/use-cases/update-user/UpdateUser.js.map +1 -0
- package/package.json +81 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,kCAAkC,CAAC;AACjD,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Domain exports - Clean Architecture Core
|
|
2
|
+
export * from './organization-memberships/index';
|
|
3
|
+
export * from './organizations/index';
|
|
4
|
+
export * from './shared/index';
|
|
5
|
+
export * from './users/index';
|
|
6
|
+
// Note: More domain slices will be added here as we expand
|
|
7
|
+
// export * from './billing/index';
|
|
8
|
+
// export * from './workflows/index';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAE3C,cAAc,kCAAkC,CAAC;AACjD,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAE9B,2DAA2D;AAC3D,mCAAmC;AACnC,qCAAqC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/organization-memberships/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,gCAAgC,EAAE,MAAM,yGAAyG,CAAC;AAChK,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/organization-memberships/index.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAIlD,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Adapters } from '@multitenantkit/domain-contracts';
|
|
2
|
+
import type { AcceptOrganizationInvitationInput, AcceptOrganizationInvitationOutput, IAcceptOrganizationInvitation } from '@multitenantkit/domain-contracts/organization-memberships';
|
|
3
|
+
import type { OperationContext, ToolkitOptions } from '@multitenantkit/domain-contracts/shared';
|
|
4
|
+
import { type DomainError } from '@multitenantkit/domain-contracts/shared/errors/index';
|
|
5
|
+
import { Result } from '../../../shared/result/Result';
|
|
6
|
+
import { BaseUseCase } from '../../../shared/use-case';
|
|
7
|
+
/**
|
|
8
|
+
* AcceptOrganizationInvitation use case
|
|
9
|
+
* Handles business logic for accepting a pending organization invitation
|
|
10
|
+
*
|
|
11
|
+
* Business Rules:
|
|
12
|
+
* - User must be registered (have a userId)
|
|
13
|
+
* - Invitation must exist and be pending (invitedAt set, joinedAt null)
|
|
14
|
+
* - Username in invitation must match user's username
|
|
15
|
+
* - Membership must not be already accepted, left, or deleted
|
|
16
|
+
*
|
|
17
|
+
* Generic support for custom fields:
|
|
18
|
+
* @template TUserCustomFields - Custom fields for UserRepository
|
|
19
|
+
* @template TOrganizationCustomFields - Custom fields for OrganizationRepository
|
|
20
|
+
* @template TOrganizationMembershipCustomFields - Custom fields for OrganizationMembershipRepository
|
|
21
|
+
*/
|
|
22
|
+
export declare class AcceptOrganizationInvitation<TOrganizationCustomFields = {}, TUserCustomFields = {}, TOrganizationMembershipCustomFields = {}> extends BaseUseCase<AcceptOrganizationInvitationInput, AcceptOrganizationInvitationOutput, DomainError, TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields> implements IAcceptOrganizationInvitation {
|
|
23
|
+
constructor(adapters: Adapters<TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields>, toolkitOptions?: ToolkitOptions<TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields>);
|
|
24
|
+
protected executeBusinessLogic(input: AcceptOrganizationInvitationInput, context: OperationContext): Promise<Result<AcceptOrganizationInvitationOutput, DomainError>>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=AcceptOrganizationInvitation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AcceptOrganizationInvitation.d.ts","sourceRoot":"","sources":["../../../../src/organization-memberships/use-cases/accept-organization-invitation/AcceptOrganizationInvitation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EACR,iCAAiC,EACjC,kCAAkC,EAClC,6BAA6B,EAChC,MAAM,2DAA2D,CAAC;AAMnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAChG,OAAO,EACH,KAAK,WAAW,EAGnB,MAAM,sDAAsD,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,4BAA4B,CAEjC,yBAAyB,GAAG,EAAE,EAE9B,iBAAiB,GAAG,EAAE,EAEtB,mCAAmC,GAAG,EAAE,CAE5C,SAAQ,WAAW,CACf,iCAAiC,EACjC,kCAAkC,EAClC,WAAW,EACX,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CAEvC,YAAW,6BAA6B;gBAGpC,QAAQ,EAAE,QAAQ,CACd,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CACtC,EACD,cAAc,CAAC,EAAE,cAAc,CAC3B,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CACtC;cAYW,oBAAoB,CAChC,KAAK,EAAE,iCAAiC,EACxC,OAAO,EAAE,gBAAgB,GAC1B,OAAO,CAAC,MAAM,CAAC,kCAAkC,EAAE,WAAW,CAAC,CAAC;CAwGtE"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { AcceptOrganizationInvitationInputSchema, AcceptOrganizationInvitationOutputSchema } from '@multitenantkit/domain-contracts/organization-memberships';
|
|
2
|
+
import { NotFoundError, ValidationError } from '@multitenantkit/domain-contracts/shared/errors/index';
|
|
3
|
+
import { Result } from '../../../shared/result/Result';
|
|
4
|
+
import { BaseUseCase } from '../../../shared/use-case';
|
|
5
|
+
/**
|
|
6
|
+
* AcceptOrganizationInvitation use case
|
|
7
|
+
* Handles business logic for accepting a pending organization invitation
|
|
8
|
+
*
|
|
9
|
+
* Business Rules:
|
|
10
|
+
* - User must be registered (have a userId)
|
|
11
|
+
* - Invitation must exist and be pending (invitedAt set, joinedAt null)
|
|
12
|
+
* - Username in invitation must match user's username
|
|
13
|
+
* - Membership must not be already accepted, left, or deleted
|
|
14
|
+
*
|
|
15
|
+
* Generic support for custom fields:
|
|
16
|
+
* @template TUserCustomFields - Custom fields for UserRepository
|
|
17
|
+
* @template TOrganizationCustomFields - Custom fields for OrganizationRepository
|
|
18
|
+
* @template TOrganizationMembershipCustomFields - Custom fields for OrganizationMembershipRepository
|
|
19
|
+
*/
|
|
20
|
+
export class AcceptOrganizationInvitation extends BaseUseCase {
|
|
21
|
+
constructor(adapters, toolkitOptions) {
|
|
22
|
+
super('organizationMembership-acceptOrganizationInvitation', adapters, toolkitOptions, AcceptOrganizationInvitationInputSchema, AcceptOrganizationInvitationOutputSchema, 'Failed to accept organization invitation');
|
|
23
|
+
}
|
|
24
|
+
async executeBusinessLogic(input, context) {
|
|
25
|
+
// 1. Get the user accepting the invitation
|
|
26
|
+
const getUserResult = await this.getUserFromExternalId(input.principalExternalId);
|
|
27
|
+
if (getUserResult.isFailure) {
|
|
28
|
+
return Result.fail(getUserResult.getError());
|
|
29
|
+
}
|
|
30
|
+
const user = getUserResult.getValue();
|
|
31
|
+
// 2. Verify username matches (security check)
|
|
32
|
+
if (user.username !== input.username) {
|
|
33
|
+
return Result.fail(new ValidationError('Username mismatch: You can only accept invitations sent to your username', 'username'));
|
|
34
|
+
}
|
|
35
|
+
// 3. Find pending invitation by username and organizationId
|
|
36
|
+
const pendingInvitation = await this.adapters.persistence.organizationMembershipRepository.findByUsernameAndOrganizationId(input.username, input.organizationId);
|
|
37
|
+
if (!pendingInvitation) {
|
|
38
|
+
return Result.fail(new NotFoundError('OrganizationInvitation', `${input.username}:${input.organizationId}`));
|
|
39
|
+
}
|
|
40
|
+
// 4. Validate invitation is pending (not already accepted, left, or deleted)
|
|
41
|
+
if (!pendingInvitation.invitedAt) {
|
|
42
|
+
return Result.fail(new ValidationError('No pending invitation found for this organization', 'organizationId'));
|
|
43
|
+
}
|
|
44
|
+
// Check leftAt and deletedAt BEFORE joinedAt, as these are terminal states
|
|
45
|
+
if (pendingInvitation.leftAt) {
|
|
46
|
+
return Result.fail(new ValidationError('Cannot accept invitation: membership was previously left', 'organizationId'));
|
|
47
|
+
}
|
|
48
|
+
if (pendingInvitation.deletedAt) {
|
|
49
|
+
return Result.fail(new ValidationError('Cannot accept invitation: invitation has been revoked', 'organizationId'));
|
|
50
|
+
}
|
|
51
|
+
if (pendingInvitation.joinedAt) {
|
|
52
|
+
return Result.fail(new ValidationError('Invitation has already been accepted', 'organizationId'));
|
|
53
|
+
}
|
|
54
|
+
// 5. Update membership: set userId and joinedAt
|
|
55
|
+
const now = this.adapters.system.clock.now();
|
|
56
|
+
const acceptedMembership = {
|
|
57
|
+
...pendingInvitation,
|
|
58
|
+
userId: user.id,
|
|
59
|
+
joinedAt: now,
|
|
60
|
+
updatedAt: now
|
|
61
|
+
};
|
|
62
|
+
// 6. Persist using Unit of Work with audit context
|
|
63
|
+
const auditContext = {
|
|
64
|
+
...context,
|
|
65
|
+
organizationId: input.organizationId,
|
|
66
|
+
auditAction: 'ACCEPT_ORGANIZATION_INVITATION'
|
|
67
|
+
};
|
|
68
|
+
try {
|
|
69
|
+
await this.adapters.persistence.uow.transaction(async (repos) => {
|
|
70
|
+
await repos.organizationMemberships.update(acceptedMembership, auditContext);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return Result.fail(new ValidationError('Failed to accept organization invitation', undefined, {
|
|
75
|
+
originalError: error
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
// 7. Return success with updated membership
|
|
79
|
+
const output = AcceptOrganizationInvitationOutputSchema.parse({
|
|
80
|
+
...acceptedMembership
|
|
81
|
+
});
|
|
82
|
+
return Result.ok(output);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=AcceptOrganizationInvitation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AcceptOrganizationInvitation.js","sourceRoot":"","sources":["../../../../src/organization-memberships/use-cases/accept-organization-invitation/AcceptOrganizationInvitation.ts"],"names":[],"mappings":"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"}
|
package/dist/organization-memberships/use-cases/add-organization-member/AddOrganizationMember.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Adapters } from '@multitenantkit/domain-contracts';
|
|
2
|
+
import type { AddOrganizationMemberInput, AddOrganizationMemberOutput, IAddOrganizationMember } from '@multitenantkit/domain-contracts/organization-memberships';
|
|
3
|
+
import type { OperationContext, ToolkitOptions } from '@multitenantkit/domain-contracts/shared';
|
|
4
|
+
import { type DomainError } from '@multitenantkit/domain-contracts/shared/errors/index';
|
|
5
|
+
import { Result } from '../../../shared/result/Result';
|
|
6
|
+
import { BaseUseCase } from '../../../shared/use-case';
|
|
7
|
+
/**
|
|
8
|
+
* AddOrganizationMember use case
|
|
9
|
+
* Handles business logic for adding a user to a organization
|
|
10
|
+
*
|
|
11
|
+
* Generic support for custom fields:
|
|
12
|
+
* @template TOrganizationCustomFields - Custom fields for OrganizationRepository (for consistency)
|
|
13
|
+
*/
|
|
14
|
+
export declare class AddOrganizationMember<TOrganizationCustomFields = {}, TUserCustomFields = {}, TOrganizationMembershipCustomFields = {}> extends BaseUseCase<AddOrganizationMemberInput, AddOrganizationMemberOutput, DomainError, TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields> implements IAddOrganizationMember {
|
|
15
|
+
constructor(adapters: Adapters<TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields>, toolkitOptions?: ToolkitOptions<TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields>);
|
|
16
|
+
protected authorize(input: AddOrganizationMemberInput, _context: OperationContext): Promise<Result<void, DomainError>>;
|
|
17
|
+
protected executeBusinessLogic(input: AddOrganizationMemberInput, context: OperationContext): Promise<Result<AddOrganizationMemberOutput, DomainError>>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=AddOrganizationMember.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AddOrganizationMember.d.ts","sourceRoot":"","sources":["../../../../src/organization-memberships/use-cases/add-organization-member/AddOrganizationMember.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EACR,0BAA0B,EAC1B,2BAA2B,EAC3B,sBAAsB,EACzB,MAAM,2DAA2D,CAAC;AAMnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAChG,OAAO,EAEH,KAAK,WAAW,EAInB,MAAM,sDAAsD,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD;;;;;;GAMG;AACH,qBAAa,qBAAqB,CAE1B,yBAAyB,GAAG,EAAE,EAE9B,iBAAiB,GAAG,EAAE,EAEtB,mCAAmC,GAAG,EAAE,CAE5C,SAAQ,WAAW,CACf,0BAA0B,EAC1B,2BAA2B,EAC3B,WAAW,EACX,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CAEvC,YAAW,sBAAsB;gBAG7B,QAAQ,EAAE,QAAQ,CACd,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CACtC,EACD,cAAc,CAAC,EAAE,cAAc,CAC3B,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CACtC;cAYW,SAAS,CACrB,KAAK,EAAE,0BAA0B,EACjC,QAAQ,EAAE,gBAAgB,GAC3B,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;cAqDrB,oBAAoB,CAChC,KAAK,EAAE,0BAA0B,EACjC,OAAO,EAAE,gBAAgB,GAC1B,OAAO,CAAC,MAAM,CAAC,2BAA2B,EAAE,WAAW,CAAC,CAAC;CAsG/D"}
|
package/dist/organization-memberships/use-cases/add-organization-member/AddOrganizationMember.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { AddOrganizationMemberInputSchema, AddOrganizationMemberOutputSchema } from '@multitenantkit/domain-contracts/organization-memberships';
|
|
2
|
+
import { ConflictError, NotFoundError, UnauthorizedError, ValidationError } from '@multitenantkit/domain-contracts/shared/errors/index';
|
|
3
|
+
import { Result } from '../../../shared/result/Result';
|
|
4
|
+
import { BaseUseCase } from '../../../shared/use-case';
|
|
5
|
+
/**
|
|
6
|
+
* AddOrganizationMember use case
|
|
7
|
+
* Handles business logic for adding a user to a organization
|
|
8
|
+
*
|
|
9
|
+
* Generic support for custom fields:
|
|
10
|
+
* @template TOrganizationCustomFields - Custom fields for OrganizationRepository (for consistency)
|
|
11
|
+
*/
|
|
12
|
+
export class AddOrganizationMember extends BaseUseCase {
|
|
13
|
+
constructor(adapters, toolkitOptions) {
|
|
14
|
+
super('organizationMembership-addOrganizationMember', adapters, toolkitOptions, AddOrganizationMemberInputSchema, AddOrganizationMemberOutputSchema, 'Failed to add organization member');
|
|
15
|
+
}
|
|
16
|
+
async authorize(input, _context) {
|
|
17
|
+
// Ensure organization exists for permission checks
|
|
18
|
+
const organization = await this.adapters.persistence.organizationRepository.findById(input.organizationId);
|
|
19
|
+
if (!organization) {
|
|
20
|
+
return Result.fail(new NotFoundError('Organization', input.organizationId));
|
|
21
|
+
}
|
|
22
|
+
// Check if organization is archived (cannot add members to archived organization)
|
|
23
|
+
if (organization.archivedAt) {
|
|
24
|
+
return Result.fail(new ValidationError('Cannot add members to an archived organization', 'organizationId'));
|
|
25
|
+
}
|
|
26
|
+
const getUserResult = await this.getUserFromExternalId(input.principalExternalId);
|
|
27
|
+
if (getUserResult.isFailure) {
|
|
28
|
+
return Result.fail(getUserResult.getError());
|
|
29
|
+
}
|
|
30
|
+
const existingUser = getUserResult.getValue();
|
|
31
|
+
// Owner can always add members
|
|
32
|
+
const isOwner = organization.ownerUserId === existingUser.id;
|
|
33
|
+
// Admin can add members (but not other admins/owners)
|
|
34
|
+
const actorMembership = await this.adapters.persistence.organizationMembershipRepository.findByUserIdAndOrganizationId(existingUser.id, input.organizationId);
|
|
35
|
+
const isAdmin = !!actorMembership &&
|
|
36
|
+
actorMembership.roleCode === 'admin' &&
|
|
37
|
+
!!actorMembership.joinedAt &&
|
|
38
|
+
!actorMembership.leftAt &&
|
|
39
|
+
!actorMembership.deletedAt;
|
|
40
|
+
const canAddRole = input.roleCode === 'member' || isOwner;
|
|
41
|
+
if (!isOwner && !(isAdmin && canAddRole)) {
|
|
42
|
+
return Result.fail(new UnauthorizedError('Only organization owners and admins can add members'));
|
|
43
|
+
}
|
|
44
|
+
return Result.ok(undefined);
|
|
45
|
+
}
|
|
46
|
+
async executeBusinessLogic(input, context) {
|
|
47
|
+
// 1. Validate target user exists
|
|
48
|
+
const targetUser = await this.adapters.persistence.userRepository.findByUsername(input.username);
|
|
49
|
+
const now = this.adapters.system.clock.now();
|
|
50
|
+
let existingMembership = null;
|
|
51
|
+
// If user doesn't exist, just create the membership without user id
|
|
52
|
+
if (targetUser) {
|
|
53
|
+
// 2. Check if user already has a membership
|
|
54
|
+
const existingMembershipDB = await this.adapters.persistence.organizationMembershipRepository.findByUserIdAndOrganizationId(targetUser.id, input.organizationId);
|
|
55
|
+
existingMembership = existingMembershipDB;
|
|
56
|
+
if (existingMembership && !existingMembership.leftAt && !existingMembership.deletedAt) {
|
|
57
|
+
return Result.fail(new ConflictError('OrganizationMembership', `${targetUser.id}:${input.organizationId}`, {
|
|
58
|
+
reason: 'User is already a member of this organization'
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
let membership;
|
|
63
|
+
if (existingMembership && !!existingMembership.leftAt && !existingMembership.deletedAt) {
|
|
64
|
+
// 4a. Reactivate existing membership that was left
|
|
65
|
+
const reactivatedMembership = {
|
|
66
|
+
...existingMembership,
|
|
67
|
+
invitedAt: now,
|
|
68
|
+
joinedAt: undefined,
|
|
69
|
+
leftAt: undefined,
|
|
70
|
+
deletedAt: undefined,
|
|
71
|
+
updatedAt: now
|
|
72
|
+
};
|
|
73
|
+
// Then update the role if it's different from current role
|
|
74
|
+
membership =
|
|
75
|
+
reactivatedMembership.roleCode !== input.roleCode
|
|
76
|
+
? { ...reactivatedMembership, roleCode: input.roleCode, updatedAt: now }
|
|
77
|
+
: reactivatedMembership;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// 4b. Create new membership
|
|
81
|
+
const membershipId = this.adapters.system.uuid.generate();
|
|
82
|
+
membership = {
|
|
83
|
+
id: membershipId,
|
|
84
|
+
userId: targetUser?.id,
|
|
85
|
+
username: input.username,
|
|
86
|
+
organizationId: input.organizationId,
|
|
87
|
+
roleCode: input.roleCode,
|
|
88
|
+
invitedAt: now,
|
|
89
|
+
joinedAt: undefined,
|
|
90
|
+
leftAt: undefined,
|
|
91
|
+
deletedAt: undefined,
|
|
92
|
+
createdAt: now,
|
|
93
|
+
updatedAt: now
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// 5. Persist the entity (save or update) using Unit of Work (transaction) with audit context
|
|
97
|
+
const auditContext = {
|
|
98
|
+
...context,
|
|
99
|
+
organizationId: input.organizationId,
|
|
100
|
+
auditAction: 'ADD_ORGANIZATION_MEMBER'
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
await this.adapters.persistence.uow.transaction(async (repos) => {
|
|
104
|
+
if (existingMembership &&
|
|
105
|
+
!!existingMembership.leftAt &&
|
|
106
|
+
!existingMembership.deletedAt) {
|
|
107
|
+
await repos.organizationMemberships.update(membership, auditContext);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
await repos.organizationMemberships.insert(membership, auditContext);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
return Result.fail(new ValidationError('Failed to save organization membership', undefined, {
|
|
116
|
+
originalError: error
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
// 6. KEY PATTERN: Spread + Parse to guarantee contract
|
|
120
|
+
const output = AddOrganizationMemberOutputSchema.parse({
|
|
121
|
+
...membership
|
|
122
|
+
});
|
|
123
|
+
return Result.ok(output);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=AddOrganizationMember.js.map
|
package/dist/organization-memberships/use-cases/add-organization-member/AddOrganizationMember.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AddOrganizationMember.js","sourceRoot":"","sources":["../../../../src/organization-memberships/use-cases/add-organization-member/AddOrganizationMember.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { AcceptOrganizationInvitation } from './accept-organization-invitation/AcceptOrganizationInvitation';
|
|
2
|
+
export { AddOrganizationMember } from './add-organization-member/AddOrganizationMember';
|
|
3
|
+
export { LeaveOrganization } from './leave-organization/LeaveOrganization';
|
|
4
|
+
export { RemoveOrganizationMember } from './remove-organization-member/RemoveOrganizationMember';
|
|
5
|
+
export { UpdateOrganizationMemberRole } from './update-organization-member-role/UpdateOrganizationMemberRole';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/organization-memberships/use-cases/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,4BAA4B,EAAE,MAAM,+DAA+D,CAAC;AAC7G,OAAO,EAAE,qBAAqB,EAAE,MAAM,iDAAiD,CAAC;AACxF,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,wBAAwB,EAAE,MAAM,uDAAuD,CAAC;AACjG,OAAO,EAAE,4BAA4B,EAAE,MAAM,gEAAgE,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Use Cases
|
|
2
|
+
export { AcceptOrganizationInvitation } from './accept-organization-invitation/AcceptOrganizationInvitation';
|
|
3
|
+
export { AddOrganizationMember } from './add-organization-member/AddOrganizationMember';
|
|
4
|
+
export { LeaveOrganization } from './leave-organization/LeaveOrganization';
|
|
5
|
+
export { RemoveOrganizationMember } from './remove-organization-member/RemoveOrganizationMember';
|
|
6
|
+
export { UpdateOrganizationMemberRole } from './update-organization-member-role/UpdateOrganizationMemberRole';
|
|
7
|
+
// DTOs
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/organization-memberships/use-cases/index.ts"],"names":[],"mappings":"AAAA,YAAY;AAEZ,OAAO,EAAE,4BAA4B,EAAE,MAAM,+DAA+D,CAAC;AAC7G,OAAO,EAAE,qBAAqB,EAAE,MAAM,iDAAiD,CAAC;AACxF,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,wBAAwB,EAAE,MAAM,uDAAuD,CAAC;AACjG,OAAO,EAAE,4BAA4B,EAAE,MAAM,gEAAgE,CAAC;AAE9G,OAAO"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Adapters } from '@multitenantkit/domain-contracts';
|
|
2
|
+
import type { ILeaveOrganization, LeaveOrganizationInput, LeaveOrganizationOutput } from '@multitenantkit/domain-contracts/organization-memberships';
|
|
3
|
+
import type { OperationContext, ToolkitOptions } from '@multitenantkit/domain-contracts/shared';
|
|
4
|
+
import { type DomainError } from '@multitenantkit/domain-contracts/shared/errors/index';
|
|
5
|
+
import { Result } from '../../../shared/result/Result';
|
|
6
|
+
import { BaseUseCase } from '../../../shared/use-case';
|
|
7
|
+
/**
|
|
8
|
+
* LeaveOrganization use case
|
|
9
|
+
* Handles business logic for a user leaving a organization
|
|
10
|
+
*/
|
|
11
|
+
export declare class LeaveOrganization<TUserCustomFields = {}, TOrganizationCustomFields = {}, TOrganizationMembershipCustomFields = {}> extends BaseUseCase<LeaveOrganizationInput, LeaveOrganizationOutput, DomainError, TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields> implements ILeaveOrganization {
|
|
12
|
+
constructor(adapters: Adapters<TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields>, toolkitOptions?: ToolkitOptions<TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields>);
|
|
13
|
+
protected executeBusinessLogic(input: LeaveOrganizationInput, context: OperationContext): Promise<Result<LeaveOrganizationOutput, DomainError>>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=LeaveOrganization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LeaveOrganization.d.ts","sourceRoot":"","sources":["../../../../src/organization-memberships/use-cases/leave-organization/LeaveOrganization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EACR,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,EAC1B,MAAM,2DAA2D,CAAC;AAMnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAChG,OAAO,EACH,KAAK,WAAW,EAGnB,MAAM,sDAAsD,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD;;;GAGG;AACH,qBAAa,iBAAiB,CAEtB,iBAAiB,GAAG,EAAE,EAEtB,yBAAyB,GAAG,EAAE,EAE9B,mCAAmC,GAAG,EAAE,CAE5C,SAAQ,WAAW,CACf,sBAAsB,EACtB,uBAAuB,EACvB,WAAW,EACX,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CAEvC,YAAW,kBAAkB;gBAGzB,QAAQ,EAAE,QAAQ,CACd,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CACtC,EACD,cAAc,CAAC,EAAE,cAAc,CAC3B,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CACtC;cAYW,oBAAoB,CAChC,KAAK,EAAE,sBAAsB,EAC7B,OAAO,EAAE,gBAAgB,GAC1B,OAAO,CAAC,MAAM,CAAC,uBAAuB,EAAE,WAAW,CAAC,CAAC;CA0E3D"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { LeaveOrganizationInputSchema, LeaveOrganizationOutputSchema } from '@multitenantkit/domain-contracts/organization-memberships';
|
|
2
|
+
import { NotFoundError, ValidationError } from '@multitenantkit/domain-contracts/shared/errors/index';
|
|
3
|
+
import { Result } from '../../../shared/result/Result';
|
|
4
|
+
import { BaseUseCase } from '../../../shared/use-case';
|
|
5
|
+
/**
|
|
6
|
+
* LeaveOrganization use case
|
|
7
|
+
* Handles business logic for a user leaving a organization
|
|
8
|
+
*/
|
|
9
|
+
export class LeaveOrganization extends BaseUseCase {
|
|
10
|
+
constructor(adapters, toolkitOptions) {
|
|
11
|
+
super('organizationMembership-leaveOrganization', adapters, toolkitOptions, LeaveOrganizationInputSchema, LeaveOrganizationOutputSchema, 'Failed to leave organization');
|
|
12
|
+
}
|
|
13
|
+
async executeBusinessLogic(input, context) {
|
|
14
|
+
// 1. Validate organization exists
|
|
15
|
+
const organization = await this.adapters.persistence.organizationRepository.findById(input.organizationId);
|
|
16
|
+
if (!organization) {
|
|
17
|
+
return Result.fail(new NotFoundError('Organization', input.organizationId));
|
|
18
|
+
}
|
|
19
|
+
const getUserResult = await this.getUserFromExternalId(input.principalExternalId);
|
|
20
|
+
if (getUserResult.isFailure) {
|
|
21
|
+
return Result.fail(getUserResult.getError());
|
|
22
|
+
}
|
|
23
|
+
const existingUser = getUserResult.getValue();
|
|
24
|
+
// 2. Validate user membership exists
|
|
25
|
+
const membership = await this.adapters.persistence.organizationMembershipRepository.findByUserIdAndOrganizationId(existingUser.id, input.organizationId);
|
|
26
|
+
if (!membership || membership.leftAt) {
|
|
27
|
+
return Result.fail(new NotFoundError('OrganizationMembership', `${existingUser.id}:${input.organizationId}`));
|
|
28
|
+
}
|
|
29
|
+
// 3. Prevent owner from leaving (must transfer ownership first)
|
|
30
|
+
if (organization.ownerUserId === existingUser.id) {
|
|
31
|
+
return Result.fail(new ValidationError('Organization owner cannot leave. Transfer ownership first.'));
|
|
32
|
+
}
|
|
33
|
+
// 4. Leave the organization (immutably)
|
|
34
|
+
const now = this.adapters.system.clock.now();
|
|
35
|
+
const leftMembership = {
|
|
36
|
+
...membership,
|
|
37
|
+
leftAt: now,
|
|
38
|
+
deletedAt: undefined,
|
|
39
|
+
updatedAt: now
|
|
40
|
+
};
|
|
41
|
+
// 5. Persist the updated entity with audit context
|
|
42
|
+
const auditContext = {
|
|
43
|
+
...context,
|
|
44
|
+
organizationId: input.organizationId,
|
|
45
|
+
auditAction: 'LEAVE_ORGANIZATION'
|
|
46
|
+
};
|
|
47
|
+
try {
|
|
48
|
+
await this.adapters.persistence.uow.transaction(async (repos) => {
|
|
49
|
+
await repos.organizationMemberships.update(leftMembership, auditContext);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return Result.fail(new ValidationError('Failed to leave organization', undefined, {
|
|
54
|
+
originalError: error
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
// 6. KEY PATTERN: Spread + Parse to guarantee contract
|
|
58
|
+
const output = LeaveOrganizationOutputSchema.parse({
|
|
59
|
+
...leftMembership
|
|
60
|
+
});
|
|
61
|
+
return Result.ok(output);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=LeaveOrganization.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LeaveOrganization.js","sourceRoot":"","sources":["../../../../src/organization-memberships/use-cases/leave-organization/LeaveOrganization.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Adapters } from '@multitenantkit/domain-contracts';
|
|
2
|
+
import type { IRemoveOrganizationMember, RemoveOrganizationMemberInput, RemoveOrganizationMemberOutput } from '@multitenantkit/domain-contracts/organization-memberships';
|
|
3
|
+
import type { OperationContext, ToolkitOptions } from '@multitenantkit/domain-contracts/shared';
|
|
4
|
+
import { type DomainError } from '@multitenantkit/domain-contracts/shared/errors/index';
|
|
5
|
+
import { Result } from '../../../shared/result/Result';
|
|
6
|
+
import { BaseUseCase } from '../../../shared/use-case';
|
|
7
|
+
/**
|
|
8
|
+
* RemoveOrganizationMember use case
|
|
9
|
+
* Handles business logic for removing a user from a organization
|
|
10
|
+
*/
|
|
11
|
+
export declare class RemoveOrganizationMember<TOrganizationCustomFields = {}, TUserCustomFields = {}, TOrganizationMembershipCustomFields = {}> extends BaseUseCase<RemoveOrganizationMemberInput, RemoveOrganizationMemberOutput, DomainError, TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields> implements IRemoveOrganizationMember {
|
|
12
|
+
constructor(adapters: Adapters<TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields>, toolkitOptions?: ToolkitOptions<TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields>);
|
|
13
|
+
protected authorize(input: RemoveOrganizationMemberInput, _context: OperationContext): Promise<Result<void, DomainError>>;
|
|
14
|
+
protected executeBusinessLogic(input: RemoveOrganizationMemberInput, context: OperationContext): Promise<Result<RemoveOrganizationMemberOutput, DomainError>>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=RemoveOrganizationMember.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RemoveOrganizationMember.d.ts","sourceRoot":"","sources":["../../../../src/organization-memberships/use-cases/remove-organization-member/RemoveOrganizationMember.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EACR,yBAAyB,EAEzB,6BAA6B,EAC7B,8BAA8B,EACjC,MAAM,2DAA2D,CAAC;AAKnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAChG,OAAO,EACH,KAAK,WAAW,EAInB,MAAM,sDAAsD,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD;;;GAGG;AACH,qBAAa,wBAAwB,CAE7B,yBAAyB,GAAG,EAAE,EAE9B,iBAAiB,GAAG,EAAE,EAEtB,mCAAmC,GAAG,EAAE,CAE5C,SAAQ,WAAW,CACf,6BAA6B,EAC7B,8BAA8B,EAC9B,WAAW,EACX,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CAEvC,YAAW,yBAAyB;gBAGhC,QAAQ,EAAE,QAAQ,CACd,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CACtC,EACD,cAAc,CAAC,EAAE,cAAc,CAC3B,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CACtC;cAYW,SAAS,CACrB,KAAK,EAAE,6BAA6B,EACpC,QAAQ,EAAE,gBAAgB,GAC3B,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;cA0ErB,oBAAoB,CAChC,KAAK,EAAE,6BAA6B,EACpC,OAAO,EAAE,gBAAgB,GAC1B,OAAO,CAAC,MAAM,CAAC,8BAA8B,EAAE,WAAW,CAAC,CAAC;CA8ClE"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { RemoveOrganizationMemberInputSchema, RemoveOrganizationMemberOutputSchema } from '@multitenantkit/domain-contracts/organization-memberships';
|
|
2
|
+
import { NotFoundError, UnauthorizedError, ValidationError } from '@multitenantkit/domain-contracts/shared/errors/index';
|
|
3
|
+
import { Result } from '../../../shared/result/Result';
|
|
4
|
+
import { BaseUseCase } from '../../../shared/use-case';
|
|
5
|
+
/**
|
|
6
|
+
* RemoveOrganizationMember use case
|
|
7
|
+
* Handles business logic for removing a user from a organization
|
|
8
|
+
*/
|
|
9
|
+
export class RemoveOrganizationMember extends BaseUseCase {
|
|
10
|
+
constructor(adapters, toolkitOptions) {
|
|
11
|
+
super('organizationMembership-removeOrganizationMember', adapters, toolkitOptions, RemoveOrganizationMemberInputSchema, RemoveOrganizationMemberOutputSchema, 'Failed to remove organization member');
|
|
12
|
+
}
|
|
13
|
+
async authorize(input, _context) {
|
|
14
|
+
// Organization must exist
|
|
15
|
+
const organization = await this.adapters.persistence.organizationRepository.findById(input.organizationId);
|
|
16
|
+
if (!organization) {
|
|
17
|
+
return Result.fail(new NotFoundError('Organization', input.organizationId));
|
|
18
|
+
}
|
|
19
|
+
const getUserResult = await this.getUserFromExternalId(input.principalExternalId);
|
|
20
|
+
if (getUserResult.isFailure) {
|
|
21
|
+
return Result.fail(getUserResult.getError());
|
|
22
|
+
}
|
|
23
|
+
const existingUser = getUserResult.getValue();
|
|
24
|
+
// Target membership must exist and be active (not left)
|
|
25
|
+
let targetMembership;
|
|
26
|
+
if (!input.removeByUsername) {
|
|
27
|
+
targetMembership =
|
|
28
|
+
await this.adapters.persistence.organizationMembershipRepository.findByUserIdAndOrganizationId(input.targetUser, input.organizationId);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
targetMembership =
|
|
32
|
+
await this.adapters.persistence.organizationMembershipRepository.findByUsernameAndOrganizationId(input.targetUser, input.organizationId);
|
|
33
|
+
}
|
|
34
|
+
if (!targetMembership || targetMembership.leftAt || targetMembership.deletedAt) {
|
|
35
|
+
return Result.fail(new NotFoundError('OrganizationMembership', `${input.targetUser}:${input.organizationId}`));
|
|
36
|
+
}
|
|
37
|
+
// Owner cannot be removed
|
|
38
|
+
if (organization.ownerUserId === targetMembership.userId) {
|
|
39
|
+
return Result.fail(new ValidationError('Organization owner cannot be removed. Transfer ownership first.'));
|
|
40
|
+
}
|
|
41
|
+
// Actor permissions: owner can remove anyone; admin can remove only members
|
|
42
|
+
const actorMembership = await this.adapters.persistence.organizationMembershipRepository.findByUserIdAndOrganizationId(existingUser.id, input.organizationId);
|
|
43
|
+
const isOwner = organization.ownerUserId === existingUser.id;
|
|
44
|
+
const isAdmin = !!actorMembership &&
|
|
45
|
+
actorMembership.roleCode === 'admin' &&
|
|
46
|
+
!!actorMembership.joinedAt &&
|
|
47
|
+
!actorMembership.leftAt &&
|
|
48
|
+
!actorMembership.deletedAt;
|
|
49
|
+
const targetIsMember = targetMembership.roleCode === 'member';
|
|
50
|
+
if (!(isOwner || (isAdmin && targetIsMember))) {
|
|
51
|
+
return Result.fail(new UnauthorizedError('Insufficient permissions to remove this member'));
|
|
52
|
+
}
|
|
53
|
+
return Result.ok(undefined);
|
|
54
|
+
}
|
|
55
|
+
async executeBusinessLogic(input, context) {
|
|
56
|
+
// Re-fetch target membership to get its ID for deletion
|
|
57
|
+
let targetMembership;
|
|
58
|
+
if (!input.removeByUsername) {
|
|
59
|
+
targetMembership =
|
|
60
|
+
await this.adapters.persistence.organizationMembershipRepository.findByUserIdAndOrganizationId(input.targetUser, input.organizationId);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
targetMembership =
|
|
64
|
+
await this.adapters.persistence.organizationMembershipRepository.findByUsernameAndOrganizationId(input.targetUser, input.organizationId);
|
|
65
|
+
}
|
|
66
|
+
if (!targetMembership) {
|
|
67
|
+
return Result.fail(new NotFoundError('OrganizationMembership', `${input.targetUser}:${input.organizationId}`));
|
|
68
|
+
}
|
|
69
|
+
const auditContext = {
|
|
70
|
+
...context,
|
|
71
|
+
organizationId: input.organizationId,
|
|
72
|
+
auditAction: 'REMOVE_ORGANIZATION_MEMBER'
|
|
73
|
+
};
|
|
74
|
+
try {
|
|
75
|
+
await this.adapters.persistence.uow.transaction(async (repos) => {
|
|
76
|
+
await repos.organizationMemberships.delete(targetMembership.id, auditContext);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return Result.fail(new ValidationError('Failed to remove organization member', undefined, {
|
|
81
|
+
originalError: error
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
return Result.ok(undefined);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=RemoveOrganizationMember.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RemoveOrganizationMember.js","sourceRoot":"","sources":["../../../../src/organization-memberships/use-cases/remove-organization-member/RemoveOrganizationMember.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Adapters } from '@multitenantkit/domain-contracts';
|
|
2
|
+
import type { IUpdateOrganizationMemberRole, UpdateOrganizationMemberRoleInput, UpdateOrganizationMemberRoleOutput } from '@multitenantkit/domain-contracts/organization-memberships';
|
|
3
|
+
import type { OperationContext, ToolkitOptions } from '@multitenantkit/domain-contracts/shared';
|
|
4
|
+
import { type DomainError } from '@multitenantkit/domain-contracts/shared/errors/index';
|
|
5
|
+
import { Result } from '../../../shared/result/Result';
|
|
6
|
+
import { BaseUseCase } from '../../../shared/use-case';
|
|
7
|
+
/**
|
|
8
|
+
* UpdateOrganizationMemberRole use case
|
|
9
|
+
* Handles business logic for updating a organization member's role
|
|
10
|
+
*/
|
|
11
|
+
export declare class UpdateOrganizationMemberRole<TOrganizationCustomFields = {}, TUserCustomFields = {}, TOrganizationMembershipCustomFields = {}> extends BaseUseCase<UpdateOrganizationMemberRoleInput, UpdateOrganizationMemberRoleOutput, DomainError, TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields> implements IUpdateOrganizationMemberRole {
|
|
12
|
+
constructor(adapters: Adapters<TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields>, toolkitOptions?: ToolkitOptions<TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields>);
|
|
13
|
+
protected authorize(input: UpdateOrganizationMemberRoleInput, _context: OperationContext): Promise<Result<void, DomainError>>;
|
|
14
|
+
protected executeBusinessLogic(input: UpdateOrganizationMemberRoleInput, context: OperationContext): Promise<Result<UpdateOrganizationMemberRoleOutput, DomainError>>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=UpdateOrganizationMemberRole.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UpdateOrganizationMemberRole.d.ts","sourceRoot":"","sources":["../../../../src/organization-memberships/use-cases/update-organization-member-role/UpdateOrganizationMemberRole.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EACR,6BAA6B,EAC7B,iCAAiC,EACjC,kCAAkC,EACrC,MAAM,2DAA2D,CAAC;AAMnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAChG,OAAO,EACH,KAAK,WAAW,EAInB,MAAM,sDAAsD,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD;;;GAGG;AACH,qBAAa,4BAA4B,CAEjC,yBAAyB,GAAG,EAAE,EAE9B,iBAAiB,GAAG,EAAE,EAEtB,mCAAmC,GAAG,EAAE,CAE5C,SAAQ,WAAW,CACf,iCAAiC,EACjC,kCAAkC,EAClC,WAAW,EACX,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CAEvC,YAAW,6BAA6B;gBAGpC,QAAQ,EAAE,QAAQ,CACd,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CACtC,EACD,cAAc,CAAC,EAAE,cAAc,CAC3B,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CACtC;cAYW,SAAS,CACrB,KAAK,EAAE,iCAAiC,EACxC,QAAQ,EAAE,gBAAgB,GAC3B,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;cAuErB,oBAAoB,CAChC,KAAK,EAAE,iCAAiC,EACxC,OAAO,EAAE,gBAAgB,GAC1B,OAAO,CAAC,MAAM,CAAC,kCAAkC,EAAE,WAAW,CAAC,CAAC;CA6CtE"}
|