feat: add auto-accept logic for team memberships based on org email domain (#26860)
## What does this PR do?
Adds auto-accept logic to the `/v2/teams/{teamId}/memberships` endpoint, similar to what exists in the `/v2/organizations/{orgId}/teams/{teamId}/memberships` endpoint.
When a user is added to a sub-team of an organization, the system now checks if the user's email domain matches the organization's `orgAutoAcceptEmail` setting. If it matches and the organization is verified, the membership is automatically accepted (overriding any `accepted: false` in the request).
### Changes:
- Modified `TeamsMembershipsService.createTeamMembership()` to check if the team has a parent organization and apply auto-accept logic using the existing `OrganizationMembershipService.shouldAutoAccept()` method
- Exported `OrganizationMembershipService` from `OrganizationsModule` for dependency injection
- Added `TeamsModule` and `UsersModule` imports to `TeamsMembershipsModule`
- Added required dependencies (`OrganizationMembershipService`, `OAuthClientRepository`, etc.) to `TeamsModule` and `TeamsSchedulesModule` since they provide `TeamsMembershipsService` directly
- Added comprehensive E2E tests for auto-accept scenarios
- Added `createOrgSettings` and `deleteOrgSettings` helper methods to test fixtures
## Mandatory Tasks (DO NOT REMOVE)
- [x] I have self-reviewed the code (A decent size PR without self-review might be rejected).
- [x] I have updated the developer docs in /docs if this PR makes changes that would require a [documentation change](https://cal.com/docs). N/A - no documentation changes needed.
- [x] I confirm automated tests are in place that prove my fix is effective or that my feature works.
## How should this be tested?
The E2E tests cover the following scenarios:
1. **Auto-accept when email matches** - User with `@acme.com` email is auto-accepted when org has `orgAutoAcceptEmail: "acme.com"`
2. **Case-insensitive matching** - User with `@ACME.COM` email matches `orgAutoAcceptEmail: "acme.com"`
3. **Override accepted:false** - Even if `accepted: false` is passed, it's overridden to `true` when email matches
4. **No auto-accept for non-matching emails** - User with `@external.com` email is NOT auto-accepted
5. **EventTypes assignment** - Verifies user is added to event types with `assignAllTeamMembers: true`
To run the tests:
```bash
TZ=UTC yarn test:e2e --testPathPattern="teams-memberships.controller.e2e-spec"
```
## Human Review Checklist
- [ ] Verify the auto-accept logic matches the existing implementation in `organizations-teams-memberships.controller.ts`
- [ ] Confirm all modules that provide `TeamsMembershipsService` directly (`TeamsModule`, `TeamsSchedulesModule`, `OrganizationsModule`) have the required dependencies
- [ ] Verify `updateNewTeamMemberEventTypes` is called when auto-accept triggers (controller checks `membership.accepted` after service returns)
- [ ] Consider if organization membership creation is needed when a user is auto-accepted into a sub-team but doesn't have org membership yet (not implemented in this PR)
## Checklist
- [x] I have read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md)
- [x] My code follows the style guidelines of this project
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have checked if my changes generate no new warnings
---
Link to Devin run: https://app.devin.ai/sessions/578b8904a5a94385b59031f9616379cc
Requested by: @hariombalhara
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { Logger, Module } from "@nestjs/common";
|
||||
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
|
||||
import { OrganizationsEventTypesPrivateLinksController } from "@/ee/event-types-private-links/controllers/organizations-event-types-private-links.controller";
|
||||
import { EventTypesPrivateLinksModule } from "@/ee/event-types-private-links/event-types-private-links.module";
|
||||
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
|
||||
import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.module";
|
||||
import { InputSchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/input-schedules.service";
|
||||
import { SchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/schedules.service";
|
||||
import { OrganizationMembershipService } from "@/lib/services/organization-membership.service";
|
||||
import { AppsRepository } from "@/modules/apps/apps.repository";
|
||||
import { ConferencingRepository } from "@/modules/conferencing/repositories/conferencing.repository";
|
||||
import { ConferencingService } from "@/modules/conferencing/services/conferencing.service";
|
||||
@@ -13,7 +15,6 @@ import { ZoomVideoService } from "@/modules/conferencing/services/zoom-video.ser
|
||||
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
|
||||
import { EmailModule } from "@/modules/email/email.module";
|
||||
import { EmailService } from "@/modules/email/email.service";
|
||||
import { OrganizationMembershipService } from "@/lib/services/organization-membership.service";
|
||||
import { MembershipsModule } from "@/modules/memberships/memberships.module";
|
||||
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
|
||||
import { UserOOORepository } from "@/modules/ooo/repositories/ooo.repository";
|
||||
@@ -37,8 +38,8 @@ import { OrganizationsRepository } from "@/modules/organizations/index/organizat
|
||||
import { OrganizationsService } from "@/modules/organizations/index/organizations.service";
|
||||
import { OrganizationsMembershipsController } from "@/modules/organizations/memberships/organizations-membership.controller";
|
||||
import { OrganizationsMembershipRepository } from "@/modules/organizations/memberships/organizations-membership.repository";
|
||||
import { OrganizationsMembershipOutputService } from "@/modules/organizations/memberships/services/organizations-membership-output.service";
|
||||
import { OrganizationsMembershipService } from "@/modules/organizations/memberships/services/organizations-membership.service";
|
||||
import { OrganizationsMembershipOutputService } from "@/modules/organizations/memberships/services/organizations-membership-output.service";
|
||||
import { OrganizationsOrganizationsModule } from "@/modules/organizations/organizations/organizations-organizations.module";
|
||||
import { OrganizationsRolesModule } from "@/modules/organizations/roles/organizations-roles.module";
|
||||
import { OrganizationsSchedulesController } from "@/modules/organizations/schedules/organizations-schedules.controller";
|
||||
@@ -84,7 +85,6 @@ import { TeamRoutingFormWorkflowsService } from "@/modules/workflows/services/te
|
||||
import { WorkflowsInputService } from "@/modules/workflows/services/workflows.input.service";
|
||||
import { WorkflowsOutputService } from "@/modules/workflows/services/workflows.output.service";
|
||||
import { WorkflowsRepository } from "@/modules/workflows/workflows.repository";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -162,6 +162,7 @@ import { Module } from "@nestjs/common";
|
||||
TeamsMembershipsService,
|
||||
TeamsMembershipsRepository,
|
||||
OAuthClientRepository,
|
||||
Logger,
|
||||
],
|
||||
exports: [
|
||||
OrganizationsService,
|
||||
@@ -184,6 +185,7 @@ import { Module } from "@nestjs/common";
|
||||
OrganizationsEventTypesService,
|
||||
OrganizationsConferencingService,
|
||||
OrganizationsStripeService,
|
||||
OrganizationMembershipService,
|
||||
],
|
||||
controllers: [
|
||||
OrganizationsTeamsController,
|
||||
@@ -202,4 +204,4 @@ import { Module } from "@nestjs/common";
|
||||
OrganizationsEventTypesPrivateLinksController,
|
||||
],
|
||||
})
|
||||
export class OrganizationsModule { }
|
||||
export class OrganizationsModule {}
|
||||
|
||||
+220
@@ -505,6 +505,226 @@ describe("Teams Memberships Endpoints", () => {
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
// Auto-accept tests for sub-teams of organizations
|
||||
describe("auto-accept based on email domain for org sub-teams", () => {
|
||||
let orgWithAutoAccept: Team;
|
||||
let subteamWithAutoAccept: Team;
|
||||
let subteamEventType: EventType;
|
||||
let userWithMatchingEmail: User;
|
||||
let userWithUppercaseEmail: User;
|
||||
let userWithMatchingEmailForOverride: User;
|
||||
let userWithNonMatchingEmail: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Create org with auto-accept settings
|
||||
orgWithAutoAccept = await teamsRepositoryFixture.create({
|
||||
name: `auto-accept-org-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
// Create organization settings with orgAutoAcceptEmail
|
||||
await teamsRepositoryFixture.createOrgSettings(orgWithAutoAccept.id, {
|
||||
orgAutoAcceptEmail: "acme.com",
|
||||
isOrganizationVerified: true,
|
||||
isOrganizationConfigured: true,
|
||||
isAdminAPIEnabled: true,
|
||||
});
|
||||
|
||||
// Create subteam
|
||||
subteamWithAutoAccept = await teamsRepositoryFixture.create({
|
||||
name: `auto-accept-subteam-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: orgWithAutoAccept.id } },
|
||||
});
|
||||
|
||||
// Create event type with assignAllTeamMembers
|
||||
subteamEventType = await eventTypesRepositoryFixture.createTeamEventType({
|
||||
schedulingType: "COLLECTIVE",
|
||||
team: { connect: { id: subteamWithAutoAccept.id } },
|
||||
title: "Auto Accept Event Type",
|
||||
slug: "auto-accept-event-type",
|
||||
length: 30,
|
||||
assignAllTeamMembers: true,
|
||||
bookingFields: [],
|
||||
locations: [],
|
||||
});
|
||||
|
||||
// Create users with different email domains
|
||||
userWithMatchingEmail = await userRepositoryFixture.create({
|
||||
email: `alice-${randomString()}@acme.com`,
|
||||
username: `alice-${randomString()}`,
|
||||
});
|
||||
|
||||
userWithUppercaseEmail = await userRepositoryFixture.create({
|
||||
email: `bob-${randomString()}@ACME.COM`,
|
||||
username: `bob-${randomString()}`,
|
||||
});
|
||||
|
||||
userWithMatchingEmailForOverride = await userRepositoryFixture.create({
|
||||
email: `david-${randomString()}@acme.com`,
|
||||
username: `david-${randomString()}`,
|
||||
});
|
||||
|
||||
userWithNonMatchingEmail = await userRepositoryFixture.create({
|
||||
email: `charlie-${randomString()}@external.com`,
|
||||
username: `charlie-${randomString()}`,
|
||||
});
|
||||
|
||||
// Add users to org
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "MEMBER",
|
||||
accepted: true,
|
||||
user: { connect: { id: userWithMatchingEmail.id } },
|
||||
team: { connect: { id: orgWithAutoAccept.id } },
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "MEMBER",
|
||||
accepted: true,
|
||||
user: { connect: { id: userWithUppercaseEmail.id } },
|
||||
team: { connect: { id: orgWithAutoAccept.id } },
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "MEMBER",
|
||||
accepted: true,
|
||||
user: { connect: { id: userWithMatchingEmailForOverride.id } },
|
||||
team: { connect: { id: orgWithAutoAccept.id } },
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "MEMBER",
|
||||
accepted: true,
|
||||
user: { connect: { id: userWithNonMatchingEmail.id } },
|
||||
team: { connect: { id: orgWithAutoAccept.id } },
|
||||
});
|
||||
|
||||
// Create profiles for users
|
||||
await profileRepositoryFixture.create({
|
||||
uid: `usr-${userWithMatchingEmail.id}`,
|
||||
username: userWithMatchingEmail.username || `user-${userWithMatchingEmail.id}`,
|
||||
organization: { connect: { id: orgWithAutoAccept.id } },
|
||||
user: { connect: { id: userWithMatchingEmail.id } },
|
||||
});
|
||||
|
||||
await profileRepositoryFixture.create({
|
||||
uid: `usr-${userWithUppercaseEmail.id}`,
|
||||
username: userWithUppercaseEmail.username || `user-${userWithUppercaseEmail.id}`,
|
||||
organization: { connect: { id: orgWithAutoAccept.id } },
|
||||
user: { connect: { id: userWithUppercaseEmail.id } },
|
||||
});
|
||||
|
||||
await profileRepositoryFixture.create({
|
||||
uid: `usr-${userWithMatchingEmailForOverride.id}`,
|
||||
username:
|
||||
userWithMatchingEmailForOverride.username || `user-${userWithMatchingEmailForOverride.id}`,
|
||||
organization: { connect: { id: orgWithAutoAccept.id } },
|
||||
user: { connect: { id: userWithMatchingEmailForOverride.id } },
|
||||
});
|
||||
|
||||
await profileRepositoryFixture.create({
|
||||
uid: `usr-${userWithNonMatchingEmail.id}`,
|
||||
username: userWithNonMatchingEmail.username || `user-${userWithNonMatchingEmail.id}`,
|
||||
organization: { connect: { id: orgWithAutoAccept.id } },
|
||||
user: { connect: { id: userWithNonMatchingEmail.id } },
|
||||
});
|
||||
|
||||
// Make teamAdmin an admin of the org and subteam for API access
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "ADMIN",
|
||||
accepted: true,
|
||||
user: { connect: { id: teamAdmin.id } },
|
||||
team: { connect: { id: orgWithAutoAccept.id } },
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "ADMIN",
|
||||
accepted: true,
|
||||
user: { connect: { id: teamAdmin.id } },
|
||||
team: { connect: { id: subteamWithAutoAccept.id } },
|
||||
});
|
||||
|
||||
await profileRepositoryFixture.create({
|
||||
uid: `usr-org-${teamAdmin.id}`,
|
||||
username: teamAdmin.username || `admin-${teamAdmin.id}`,
|
||||
organization: { connect: { id: orgWithAutoAccept.id } },
|
||||
user: { connect: { id: teamAdmin.id } },
|
||||
});
|
||||
});
|
||||
|
||||
it("should auto-accept when email matches orgAutoAcceptEmail for sub-team", async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.post(`/v2/teams/${subteamWithAutoAccept.id}/memberships`)
|
||||
.send({
|
||||
userId: userWithMatchingEmail.id,
|
||||
role: "MEMBER",
|
||||
} satisfies CreateTeamMembershipInput)
|
||||
.expect(201);
|
||||
|
||||
const responseBody: CreateTeamMembershipOutput = response.body;
|
||||
expect(responseBody.data.accepted).toBe(true);
|
||||
|
||||
// Verify EventTypes assignment
|
||||
const eventTypes = await eventTypesRepositoryFixture.getAllTeamEventTypes(subteamWithAutoAccept.id);
|
||||
const eventTypeWithAssignAll = eventTypes.find((et) => et.assignAllTeamMembers);
|
||||
expect(eventTypeWithAssignAll).toBeTruthy();
|
||||
const userIsHost = eventTypeWithAssignAll?.hosts.some((h) => h.userId === userWithMatchingEmail.id);
|
||||
expect(userIsHost).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle case-insensitive email domain matching for sub-team", async () => {
|
||||
// User with email="bob@ACME.COM" should match orgAutoAcceptEmail="acme.com"
|
||||
const response = await request(app.getHttpServer())
|
||||
.post(`/v2/teams/${subteamWithAutoAccept.id}/memberships`)
|
||||
.send({
|
||||
userId: userWithUppercaseEmail.id,
|
||||
role: "MEMBER",
|
||||
} satisfies CreateTeamMembershipInput)
|
||||
.expect(201);
|
||||
|
||||
const responseBody: CreateTeamMembershipOutput = response.body;
|
||||
expect(responseBody.data.accepted).toBe(true);
|
||||
});
|
||||
|
||||
it("should ALWAYS auto-accept when email matches, even if accepted:false for sub-team", async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.post(`/v2/teams/${subteamWithAutoAccept.id}/memberships`)
|
||||
.send({
|
||||
userId: userWithMatchingEmailForOverride.id,
|
||||
role: "MEMBER",
|
||||
accepted: false,
|
||||
} satisfies CreateTeamMembershipInput)
|
||||
.expect(201);
|
||||
|
||||
const responseBody: CreateTeamMembershipOutput = response.body;
|
||||
// Should override to true because email matches
|
||||
expect(responseBody.data.accepted).toBe(true);
|
||||
});
|
||||
|
||||
it("should NOT auto-accept when email does not match orgAutoAcceptEmail for sub-team", async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.post(`/v2/teams/${subteamWithAutoAccept.id}/memberships`)
|
||||
.send({
|
||||
userId: userWithNonMatchingEmail.id,
|
||||
role: "MEMBER",
|
||||
} satisfies CreateTeamMembershipInput)
|
||||
.expect(201);
|
||||
|
||||
const responseBody: CreateTeamMembershipOutput = response.body;
|
||||
expect(responseBody.data.accepted).toBe(false);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await userRepositoryFixture.deleteByEmail(userWithMatchingEmail.email);
|
||||
await userRepositoryFixture.deleteByEmail(userWithUppercaseEmail.email);
|
||||
await userRepositoryFixture.deleteByEmail(userWithMatchingEmailForOverride.email);
|
||||
await userRepositoryFixture.deleteByEmail(userWithNonMatchingEmail.email);
|
||||
await teamsRepositoryFixture.deleteOrgSettings(orgWithAutoAccept.id);
|
||||
await teamsRepositoryFixture.delete(subteamWithAutoAccept.id);
|
||||
await teamsRepositoryFixture.delete(orgWithAutoAccept.id);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await userRepositoryFixture.deleteByEmail(teamAdmin.email);
|
||||
await userRepositoryFixture.deleteByEmail(teammateInvitedViaApi.email);
|
||||
|
||||
+19
-27
@@ -1,3 +1,22 @@
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { updateNewTeamMemberEventTypes } from "@calcom/platform-libraries/event-types";
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Logger,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { plainToClass } from "class-transformer";
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { API_KEY_HEADER } from "@/lib/docs/headers";
|
||||
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||
@@ -13,26 +32,6 @@ import { GetTeamMembershipsOutput } from "@/modules/teams/memberships/outputs/ge
|
||||
import { TeamMembershipOutput } from "@/modules/teams/memberships/outputs/team-membership.output";
|
||||
import { UpdateTeamMembershipOutput } from "@/modules/teams/memberships/outputs/update-team-membership.output";
|
||||
import { TeamsMembershipsService } from "@/modules/teams/memberships/services/teams-memberships.service";
|
||||
import {
|
||||
Controller,
|
||||
UseGuards,
|
||||
Get,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Query,
|
||||
Delete,
|
||||
Patch,
|
||||
Post,
|
||||
Body,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Logger,
|
||||
} from "@nestjs/common";
|
||||
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { plainToClass } from "class-transformer";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { updateNewTeamMemberEventTypes } from "@calcom/platform-libraries/event-types";
|
||||
|
||||
@Controller({
|
||||
path: "/v2/teams/:teamId/memberships",
|
||||
@@ -55,13 +54,6 @@ export class TeamsMembershipsController {
|
||||
@Body() body: CreateTeamMembershipInput
|
||||
): Promise<CreateTeamMembershipOutput> {
|
||||
const membership = await this.teamsMembershipsService.createTeamMembership(teamId, body);
|
||||
if (membership.accepted) {
|
||||
try {
|
||||
await updateNewTeamMemberEventTypes(body.userId, teamId);
|
||||
} catch (err) {
|
||||
this.logger.error("Could not update new team member eventTypes", err);
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: plainToClass(TeamMembershipOutput, membership, { strategy: "excludeAll" }),
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { TeamService } from "@calcom/platform-libraries";
|
||||
import { updateNewTeamMemberEventTypes } from "@calcom/platform-libraries/event-types";
|
||||
import { BadRequestException, Injectable, Logger, NotFoundException } from "@nestjs/common";
|
||||
import { OrganizationMembershipService } from "@/lib/services/organization-membership.service";
|
||||
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
|
||||
import { CreateTeamMembershipInput } from "@/modules/teams/memberships/inputs/create-team-membership.input";
|
||||
import { UpdateTeamMembershipInput } from "@/modules/teams/memberships/inputs/update-team-membership.input";
|
||||
import { TeamsMembershipsRepository } from "@/modules/teams/memberships/teams-memberships.repository";
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
|
||||
import { TeamService } from "@calcom/platform-libraries";
|
||||
import { TeamsRepository } from "@/modules/teams/teams/teams.repository";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
|
||||
export const PLATFORM_USER_BEING_ADDED_TO_REGULAR_TEAM_ERROR = `Can't add user to team - the user is platform managed user but team is not because team probably was not created using OAuth credentials.`;
|
||||
export const REGULAR_USER_BEING_ADDED_TO_PLATFORM_TEAM_ERROR = `Can't add user to team - the user is not platform managed user but team is platform managed. Both have to be created using OAuth credentials.`;
|
||||
@@ -14,11 +17,44 @@ export const PLATFORM_USER_AND_PLATFORM_TEAM_CREATED_WITH_DIFFERENT_OAUTH_CLIENT
|
||||
export class TeamsMembershipsService {
|
||||
constructor(
|
||||
private readonly teamsMembershipsRepository: TeamsMembershipsRepository,
|
||||
private readonly oAuthClientsRepository: OAuthClientRepository
|
||||
private readonly oAuthClientsRepository: OAuthClientRepository,
|
||||
private readonly teamsRepository: TeamsRepository,
|
||||
private readonly usersRepository: UsersRepository,
|
||||
private readonly orgMembershipService: OrganizationMembershipService,
|
||||
private readonly logger: Logger
|
||||
) {}
|
||||
|
||||
private async shouldAutoAccept({ teamId, userId }: { teamId: number; userId: number }): Promise<boolean> {
|
||||
const team = await this.teamsRepository.getById(teamId);
|
||||
|
||||
if (team?.parentId) {
|
||||
const user = await this.usersRepository.findById(userId);
|
||||
if (user) {
|
||||
const shouldAutoAccept = await this.orgMembershipService.shouldAutoAccept({
|
||||
organizationId: team.parentId,
|
||||
userEmail: user.email,
|
||||
});
|
||||
|
||||
return shouldAutoAccept;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
async createTeamMembership(teamId: number, data: CreateTeamMembershipInput) {
|
||||
await this.canUserBeAddedToTeam(data.userId, teamId);
|
||||
const shouldAutoAccept = await this.shouldAutoAccept({ teamId, userId: data.userId });
|
||||
if (shouldAutoAccept) {
|
||||
data = { ...data, accepted: true };
|
||||
}
|
||||
|
||||
if (data.accepted) {
|
||||
try {
|
||||
await updateNewTeamMemberEventTypes(data.userId, teamId);
|
||||
} catch (err) {
|
||||
this.logger.error("Could not update new team member eventTypes", err);
|
||||
}
|
||||
}
|
||||
const teamMembership = await this.teamsMembershipsRepository.createTeamMembership(teamId, data);
|
||||
return teamMembership;
|
||||
}
|
||||
|
||||
@@ -6,11 +6,21 @@ import { TeamsEventTypesModule } from "@/modules/teams/event-types/teams-event-t
|
||||
import { TeamsMembershipsController } from "@/modules/teams/memberships/controllers/teams-memberships.controller";
|
||||
import { TeamsMembershipsService } from "@/modules/teams/memberships/services/teams-memberships.service";
|
||||
import { TeamsMembershipsRepository } from "@/modules/teams/memberships/teams-memberships.repository";
|
||||
import { Module } from "@nestjs/common";
|
||||
import { TeamsModule } from "@/modules/teams/teams/teams.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { Logger, Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, RedisModule, OrganizationsModule, MembershipsModule, TeamsEventTypesModule],
|
||||
providers: [TeamsMembershipsRepository, TeamsMembershipsService],
|
||||
imports: [
|
||||
PrismaModule,
|
||||
RedisModule,
|
||||
OrganizationsModule,
|
||||
MembershipsModule,
|
||||
TeamsEventTypesModule,
|
||||
TeamsModule,
|
||||
UsersModule,
|
||||
],
|
||||
providers: [TeamsMembershipsRepository, TeamsMembershipsService, Logger],
|
||||
controllers: [TeamsMembershipsController],
|
||||
exports: [TeamsMembershipsService],
|
||||
})
|
||||
|
||||
@@ -10,7 +10,6 @@ import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RedisModule } from "@/modules/redis/redis.module";
|
||||
import { TeamsEventTypesModule } from "@/modules/teams/event-types/teams-event-types.module";
|
||||
import { StripeService } from "@/modules/stripe/stripe.service";
|
||||
import { TeamsMembershipsService } from "@/modules/teams/memberships/services/teams-memberships.service";
|
||||
import { TeamsMembershipsRepository } from "@/modules/teams/memberships/teams-memberships.repository";
|
||||
import { TeamsSchedulesController } from "@/modules/teams/schedules/controllers/teams-schedules.controller";
|
||||
import { TeamsSchedulesService } from "@/modules/teams/schedules/services/teams-schedules.service";
|
||||
@@ -26,7 +25,6 @@ import { Module } from "@nestjs/common";
|
||||
TeamsRepository,
|
||||
TeamsService,
|
||||
TeamsMembershipsRepository,
|
||||
TeamsMembershipsService,
|
||||
OutputSchedulesService_2024_06_11,
|
||||
OrganizationSchedulesRepository,
|
||||
StripeService,
|
||||
|
||||
@@ -2,7 +2,6 @@ import { MembershipsModule } from "@/modules/memberships/memberships.module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RedisModule } from "@/modules/redis/redis.module";
|
||||
import { StripeModule } from "@/modules/stripe/stripe.module";
|
||||
import { TeamsMembershipsService } from "@/modules/teams/memberships/services/teams-memberships.service";
|
||||
import { TeamsMembershipsRepository } from "@/modules/teams/memberships/teams-memberships.repository";
|
||||
import { TeamsController } from "@/modules/teams/teams/controllers/teams.controller";
|
||||
import { TeamsService } from "@/modules/teams/teams/services/teams.service";
|
||||
@@ -11,7 +10,7 @@ import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, MembershipsModule, RedisModule, StripeModule],
|
||||
providers: [TeamsRepository, TeamsService, TeamsMembershipsRepository, TeamsMembershipsService],
|
||||
providers: [TeamsRepository, TeamsService, TeamsMembershipsRepository],
|
||||
controllers: [TeamsController],
|
||||
exports: [TeamsRepository],
|
||||
})
|
||||
|
||||
@@ -33,4 +33,30 @@ export class TeamRepositoryFixture {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async createOrgSettings(
|
||||
organizationId: number,
|
||||
settings: {
|
||||
orgAutoAcceptEmail: string;
|
||||
isOrganizationVerified?: boolean;
|
||||
isOrganizationConfigured?: boolean;
|
||||
isAdminAPIEnabled?: boolean;
|
||||
}
|
||||
) {
|
||||
return this.prismaWriteClient.organizationSettings.create({
|
||||
data: {
|
||||
organizationId,
|
||||
orgAutoAcceptEmail: settings.orgAutoAcceptEmail,
|
||||
isOrganizationVerified: settings.isOrganizationVerified,
|
||||
isOrganizationConfigured: settings.isOrganizationConfigured,
|
||||
isAdminAPIEnabled: settings.isAdminAPIEnabled,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async deleteOrgSettings(organizationId: number) {
|
||||
return this.prismaWriteClient.organizationSettings.deleteMany({
|
||||
where: { organizationId },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user