* feat: add 5 new workflow triggers for booking events
- Add BOOKING_REJECTED, BOOKING_REQUESTED, BOOKING_PAYMENT_INITIATED, BOOKING_PAID, BOOKING_NO_SHOW_UPDATED to WorkflowTriggerEvents enum
- Update workflow constants to include new trigger options
- Implement workflow trigger logic for booking rejected and requested events
- Add translations for new workflow triggers following {enum}_trigger format
- Generate updated Prisma types for new schema changes
Co-Authored-By: amit@cal.com <samit91848@gmail.com>
* fix: type check, remove as any
* feat: add workflow trigger for BOOKING_REQUESTED in handleNewBooking.ts
- Add WorkflowTriggerEvents import to handleNewBooking.ts
- Implement workflow trigger logic for BOOKING_REQUESTED in else block
- Filter workflows by BOOKING_REQUESTED trigger and call scheduleWorkflowReminders
- Use proper calendar event object construction without type casting
- Add error handling for workflow reminder scheduling
Co-Authored-By: amit@cal.com <samit91848@gmail.com>
* fix: resolve type errors in workflow trigger implementations
- Add proper database includes for user information in handleConfirmation.ts
- Fix ExtendedCalendarEvent type structure with correct hosts mapping
- Add missing properties to calendar event objects in handleMarkNoShow.ts
- Ensure all workflow triggers follow proper type patterns
Co-Authored-By: amit@cal.com <samit91848@gmail.com>
* feat: add workflow test configurations for new booking triggers
- Add workflow configurations for BOOKING_REQUESTED and BOOKING_PAYMENT_INITIATED in fresh-booking.test.ts
- Add workflow configuration for BOOKING_REJECTED in confirm.handler.test.ts
- Enable previously skipped confirm.handler.test.ts
- Remove workflow test assertions temporarily until triggers are fully functional
- Maintain webhook test coverage while adding workflow test infrastructure
Co-Authored-By: amit@cal.com <samit91848@gmail.com>
* fix: add missing mockSuccessfulVideoMeetingCreation import to confirm.handler.test.ts
- Import mockSuccessfulVideoMeetingCreation from bookingScenario utils
- Add mock call to BOOKING_REJECTED workflow test case
- Resolves ReferenceError that was causing unit test CI failure
Co-Authored-By: amit@cal.com <samit91848@gmail.com>
* add new triggers
* refactor: improve _scheduleWorkflowReminders readability and add missing booking trigger events
- Extract complex conditional logic into helper functions (isImmediateTrigger, isTimeBased, shouldProcessWorkflow)
- Add missing workflow trigger events with immediate execution logic
- Update test workflows to use different actions (EMAIL_ATTENDEE, SMS_ATTENDEE) for better differentiation
- Fix translation function mock in confirm.handler.test.ts using mockNoTranslations utility
- Maintain existing functionality while improving code maintainability
Co-Authored-By: amit@cal.com <samit91848@gmail.com>
* only show customt emplate for form triggers
* filter outside scheduleWorkflowReminder
* fix type check
* chore: add more tests
* test: add comprehensive unit tests for handleMarkNoShow with webhook and workflow coverage
- Create handleMarkNoShow.test.ts following confirm.handler.test.ts pattern
- Add expectBookingNoShowUpdatedWebhookToHaveBeenFired utility function
- Test both webhook and workflow triggers for BOOKING_NO_SHOW_UPDATED
- Cover attendee/host no-show scenarios, multiple attendees, and error cases
- All 6 unit tests pass with proper mocking of external dependencies
Co-Authored-By: amit@cal.com <samit91848@gmail.com>
* Revert "test: add comprehensive unit tests for handleMarkNoShow with webhook and workflow coverage"
This reverts commit 764299220279f0c012392dec24d3150246bfc4ad.
* fix: add new workflow triggers to api/v2
* update swagger docs
* fix: e2e
* fix type check
* fix tests, add test for before after events
* fix unit tests
* revert confirm.handler.test
* fix: unit tests
* dummy form variables
* add routing forms to active on dropdown
* add migration file
* Ui fixes for variables dropdown
* remove other translation keys
* review fixes
* allow routing forms for activeOn
* use repository function to get routing forms
* remove unnecessary code
* adjust logic in update handler
* add triggers to api v2
* remvoe unused file
* rename to getAcitveOnOptions handler
* remove routingFormOptions handler
* clean up getActiveOnOptions
* refactor WorkflowService
* remove logs
* remove unused
* fix: type check
* fix: missed before after events for recurring
* fix: calendarEvent handleMarkNoShow
* fix error message
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* don't query disabled routing forms
* create tasker function
* add tasker code
* move isFormTrigger function
* small adjustments + todo comments
* remove email to host action for form triggers
* throw trpc error if email to host is added as step
* fix dialog on how to use form responses as variables
* remove add variable dropdown for form triggers
* remove form workfows in event workflows tab
* improvements for workflow logic on form submission
* review fixes
* base setup for seperate schedule functions (evt and form)
* add missing BOOKING_PAID workflow trigger
* fix pathname
* fix: test for BOOKING_REQUESTED
* fix activeOn ids
* pass hideBranding and smsReminderNumber
* adjustments to reminderScheduler
* create empty scheduelForForm functions
* pass locale and timezone with form user
* pass formData instead of responses
* pass timeFormat and locale
* reusable function for email sending and reminder creation
* implement scheduleEmailReminderForForm
* remove added editor field from merge conflict
* don't support cal.ai action with form triggers
* throw bad request if form trigger and cal.ai is combined
* add tests for scheduleFormWorkflows
* add form submission tests
* remove form response varibe info
* clean up workflow actions
* fixes for getting template options
* pass triggerType to getAllWorkflows
* move reusable logic to scheduleSMSReminder
* add formdata to param type
* type fixes for text reminder managers
* implement scheduleSMSReminderForForm
* fix import
* fix isAuthorizedToAddActiveOnIds
* disble whatsapp action
* implement triggerFormSubmittedNoEventWorkflow
* code clean up
* Merge branch 'devin/1755107037-add-workflow-triggers' into feat/routing-form-workflow-triggers
* fix type errors
* remove async from getSubmitterEmail
* fix type errors
* revert cal.ai changes
* fix type error
* add sublogger
* code clean up
* fix type errors
* remove label for attendee whatsapp action
* code clean up
* fixes saving teams on org workflows
* fix type error
* code improvements for activeOn ids
* Revert "code improvements for activeOn ids"
This reverts commit 0a3590a4e2ce541b17d63483ad86ed458795a6a3.
* improve variable name
* fix unit tests
* small fixes
* type fixes
* remove unused translation keys
* fix merge conflict issues
* code clean up
* remove SMS action support
* remove more SMS code
* add missing imports
* set custom template for form action
* type fixes
* fix tasker endpoint
* fix duplicate check
* fix workfows.test.ts
* use repository funciton to getHideBranding
* code clean up
* fix hasDuplicateSubmission
* code clean up
* select only needed properties
* remove repository functions
* Revert "remove repository functions"
This reverts commit 7aa47b1c59c9abd7f964ebf26f746934c53a44f4.
* add scheduleWorkflows function
* Revert "add scheduleWorkflows function"
This reverts commit fe5db4fe3b65e2743c95475d585300cab98beed7.
* move type to /types
* Revert "move type to /types"
This reverts commit 91e0152154594b3772a801a426260068a8ccea54.
* revert changes causing type errors
* remove import
* remove unused import
* Revert "remove unused import"
This reverts commit 1916768c875ea5d0ac5598ccb8f9c796c5622dc9.
* revert changed from attempt to fix type errors
* pass object to gt all workflows
* fix isAuthorized check
* trigger filtering
* remove form submitted no event booked code
* remove form submitted no event from schema
* remove more code
* remove test
* fixes
* add getSubmitterEmail function
* add missing workflow DTOs
* small fixes
* use activeOnWithChildren
* fix active on when switching trigger type
* remove add variable dropdown
* add getAllWorkflowsFromRoutingForm to WorkflowService
* fix error caused by undefined evt
* fix type error
* fix type error
* fix tests
* code clean up
* remove console.log
* remove template text form from triggers
* add routing form repoditory function
* fix bug with key
* add missing trigger in update-workflow.input.ts
* ForEvt and ForForm function for aiPhoneCallManager
* chore: add support for form workflows on api v2
* fixup! chore: add support for form workflows on api v2
* use only repository functions in update handler
* move all prisma queries from list.handler
* review suggestions
* chore: handle workflows api v2
* chore: handle workflows api v2, split in 2 endpoints
* fix workflow step creation
* remove connect agent and fixes types
* add type to workflow
* chore: use workflow type in apiv2 WorkflowsOutputService
* update worklfow type on update
* chore: use workflow type in apiv2 WorkflowsOutputService
* fix template body for torm trigger
* some UI fixes for email subject/body
* resetting email body when changing form triggers
* use type field to query workflows
* clean up all old active on values
* remove responseId from all funciton calls
* remove undefined from updateTemplate
* refactor: split routing form and event-type workflows code
* refactor: split routing form and event-type workflows code
* fix template text when adding action
* chore: don't rename WorkflowActivationDto to avoid ci blocking
* refine update schedule to use only allowed actions
* fix type error
* don't allow whatsapp action with form trigger
* fix type error
* return early if activeOn array is empty
* fix: from step type in BaseFormWorkflowStepDto
* fixup! fix: from step type in BaseFormWorkflowStepDto
* move all prisma calls to repository (service/workflows.ts)
* use FORM_TRIGGER_WORKFLOW_EVENTS for form queries
* use userRepository
* use FORM_TRIGGER_WORKFLOW_EVENTS in isFormTrigger
* code clean up
* code clean up
* add back trpc import
* fix agent repository functions
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: amit@cal.com <samit91848@gmail.com>
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Benny Joo <sldisek783@gmail.com>
Co-authored-by: cal.com <morgan@cal.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
489 lines
13 KiB
TypeScript
489 lines
13 KiB
TypeScript
import type { z } from "zod";
|
|
|
|
import { whereClauseForOrgWithSlugOrRequestedSlug } from "@calcom/ee/organizations/lib/orgDomains";
|
|
import logger from "@calcom/lib/logger";
|
|
import type { PrismaClient } from "@calcom/prisma";
|
|
import { prisma } from "@calcom/prisma";
|
|
import type { Prisma } from "@calcom/prisma/client";
|
|
import { MembershipRole } from "@calcom/prisma/enums";
|
|
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
|
|
|
import { getParsedTeam } from "./teamUtils";
|
|
|
|
type TeamGetPayloadWithParsedMetadata<TeamSelect extends Prisma.TeamSelect> =
|
|
| (Omit<Prisma.TeamGetPayload<{ select: TeamSelect }>, "metadata" | "isOrganization"> & {
|
|
metadata: z.infer<typeof teamMetadataSchema>;
|
|
isOrganization: boolean;
|
|
})
|
|
| null;
|
|
|
|
type GetTeamOrOrgArg<TeamSelect extends Prisma.TeamSelect> = {
|
|
lookupBy: (
|
|
| {
|
|
id: number;
|
|
}
|
|
| {
|
|
slug: string;
|
|
}
|
|
) & {
|
|
havingMemberWithId?: number;
|
|
};
|
|
/**
|
|
* If we are fetching a team, this is the slug of the organization that the team belongs to.
|
|
*/
|
|
forOrgWithSlug: string | null;
|
|
/**
|
|
* If true, means that we need to fetch an organization with the given slug. Otherwise, we need to fetch a team with the given slug.
|
|
*/
|
|
isOrg: boolean;
|
|
teamSelect: TeamSelect;
|
|
};
|
|
|
|
const log = logger.getSubLogger({ prefix: ["repository", "team"] });
|
|
|
|
/**
|
|
* Gets the team or organization with the given slug or id reliably along with parsed metadata.
|
|
*/
|
|
async function getTeamOrOrg<TeamSelect extends Prisma.TeamSelect>({
|
|
lookupBy,
|
|
forOrgWithSlug: forOrgWithSlug,
|
|
isOrg,
|
|
teamSelect,
|
|
}: GetTeamOrOrgArg<TeamSelect>): Promise<TeamGetPayloadWithParsedMetadata<TeamSelect>> {
|
|
const where: Prisma.TeamFindFirstArgs["where"] = {};
|
|
teamSelect = {
|
|
...teamSelect,
|
|
metadata: true,
|
|
isOrganization: true,
|
|
} satisfies TeamSelect;
|
|
if (lookupBy.havingMemberWithId) where.members = { some: { userId: lookupBy.havingMemberWithId } };
|
|
|
|
if ("id" in lookupBy) {
|
|
where.id = lookupBy.id;
|
|
} else {
|
|
where.slug = lookupBy.slug;
|
|
}
|
|
|
|
if (isOrg) {
|
|
// We must fetch only the organization here.
|
|
// Note that an organization and a team that doesn't belong to an organization, both have parentId null
|
|
// If the organization has null slug(but requestedSlug is 'test') and the team also has slug 'test', we can't distinguish them without explicitly checking the metadata.isOrganization
|
|
// Note that, this isn't possible now to have same requestedSlug as the slug of a team not part of an organization. This is legacy teams handling mostly. But it is still safer to be sure that you are fetching an Organization only in case of isOrgView
|
|
where.isOrganization = true;
|
|
// We must fetch only the team here.
|
|
} else {
|
|
if (forOrgWithSlug) {
|
|
where.parent = whereClauseForOrgWithSlugOrRequestedSlug(forOrgWithSlug);
|
|
}
|
|
}
|
|
|
|
log.debug({
|
|
orgSlug: forOrgWithSlug,
|
|
teamLookupBy: lookupBy,
|
|
isOrgView: isOrg,
|
|
where,
|
|
});
|
|
|
|
const teams = await prisma.team.findMany({
|
|
where,
|
|
select: teamSelect,
|
|
});
|
|
|
|
const teamsWithParsedMetadata = teams
|
|
.map((team) => {
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore ts types are way too complciated for this now
|
|
const parsedMetadata = teamMetadataSchema.parse(team.metadata ?? {});
|
|
return {
|
|
...team,
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore It does exist
|
|
isOrganization: team.isOrganization as boolean,
|
|
metadata: parsedMetadata,
|
|
};
|
|
})
|
|
// In cases where there are many teams with the same slug, we need to find out the one and only one that matches our criteria
|
|
.filter((team) => {
|
|
// We need an org if isOrgView otherwise we need a team
|
|
return isOrg ? team.isOrganization : !team.isOrganization;
|
|
});
|
|
|
|
if (teamsWithParsedMetadata.length > 1) {
|
|
log.error("Found more than one team/Org. We should be doing something wrong.", {
|
|
isOrgView: isOrg,
|
|
where,
|
|
teams: teamsWithParsedMetadata.map((team) => {
|
|
const t = team as unknown as { id: number; slug: string };
|
|
return {
|
|
id: t.id,
|
|
slug: t.slug,
|
|
};
|
|
}),
|
|
});
|
|
}
|
|
|
|
const team = teamsWithParsedMetadata[0];
|
|
if (!team) return null;
|
|
// HACK: I am not sure how to make Prisma in peace with TypeScript with this repository pattern
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
return team as any;
|
|
}
|
|
|
|
export async function getTeam<TeamSelect extends Prisma.TeamSelect>({
|
|
lookupBy,
|
|
forOrgWithSlug: forOrgWithSlug,
|
|
teamSelect,
|
|
}: Omit<GetTeamOrOrgArg<TeamSelect>, "isOrg">): Promise<TeamGetPayloadWithParsedMetadata<TeamSelect>> {
|
|
return getTeamOrOrg({
|
|
lookupBy,
|
|
forOrgWithSlug: forOrgWithSlug,
|
|
isOrg: false,
|
|
teamSelect,
|
|
});
|
|
}
|
|
|
|
export async function getOrg<TeamSelect extends Prisma.TeamSelect>({
|
|
lookupBy,
|
|
forOrgWithSlug: forOrgWithSlug,
|
|
teamSelect,
|
|
}: Omit<GetTeamOrOrgArg<TeamSelect>, "isOrg">): Promise<TeamGetPayloadWithParsedMetadata<TeamSelect>> {
|
|
return getTeamOrOrg({
|
|
lookupBy,
|
|
forOrgWithSlug: forOrgWithSlug,
|
|
isOrg: true,
|
|
teamSelect,
|
|
});
|
|
}
|
|
|
|
const teamSelect = {
|
|
id: true,
|
|
name: true,
|
|
slug: true,
|
|
logoUrl: true,
|
|
parentId: true,
|
|
metadata: true,
|
|
isOrganization: true,
|
|
organizationSettings: true,
|
|
isPlatform: true,
|
|
} satisfies Prisma.TeamSelect;
|
|
|
|
export class TeamRepository {
|
|
constructor(private prismaClient: PrismaClient) {}
|
|
|
|
async findById({ id }: { id: number }) {
|
|
const team = await this.prismaClient.team.findUnique({
|
|
where: {
|
|
id,
|
|
},
|
|
select: teamSelect,
|
|
});
|
|
if (!team) {
|
|
return null;
|
|
}
|
|
return getParsedTeam(team);
|
|
}
|
|
|
|
async findByIdIncludePlatformBilling({ id }: { id: number }) {
|
|
const team = await this.prismaClient.team.findUnique({
|
|
where: {
|
|
id,
|
|
},
|
|
select: { ...teamSelect, platformBilling: true },
|
|
});
|
|
if (!team) {
|
|
return null;
|
|
}
|
|
return getParsedTeam(team);
|
|
}
|
|
|
|
async findAllByParentId({
|
|
parentId,
|
|
select = teamSelect,
|
|
}: {
|
|
parentId: number;
|
|
select?: Prisma.TeamSelect;
|
|
}) {
|
|
return await this.prismaClient.team.findMany({
|
|
where: {
|
|
parentId,
|
|
},
|
|
select,
|
|
});
|
|
}
|
|
|
|
async findByIdAndParentId({
|
|
id,
|
|
parentId,
|
|
select = teamSelect,
|
|
}: {
|
|
id: number;
|
|
parentId: number;
|
|
select?: Prisma.TeamSelect;
|
|
}) {
|
|
return await this.prismaClient.team.findFirst({
|
|
where: {
|
|
id,
|
|
parentId,
|
|
},
|
|
select,
|
|
});
|
|
}
|
|
|
|
async findFirstBySlugAndParentSlug({
|
|
slug,
|
|
parentSlug,
|
|
select = teamSelect,
|
|
}: {
|
|
slug: string;
|
|
parentSlug: string | null;
|
|
select?: Prisma.TeamSelect;
|
|
}) {
|
|
return await this.prismaClient.team.findFirst({
|
|
where: {
|
|
slug,
|
|
parent: parentSlug ? whereClauseForOrgWithSlugOrRequestedSlug(parentSlug) : null,
|
|
},
|
|
select,
|
|
});
|
|
}
|
|
|
|
async deleteById({ id }: { id: number }) {
|
|
const deletedTeam = await this.prismaClient.$transaction(async (tx) => {
|
|
await tx.eventType.deleteMany({
|
|
where: {
|
|
teamId: id,
|
|
schedulingType: "MANAGED",
|
|
},
|
|
});
|
|
|
|
// delete all memberships
|
|
await tx.membership.deleteMany({
|
|
where: {
|
|
teamId: id,
|
|
},
|
|
});
|
|
|
|
const deletedTeam = await tx.team.delete({
|
|
where: {
|
|
id: id,
|
|
},
|
|
});
|
|
|
|
return deletedTeam;
|
|
});
|
|
|
|
return deletedTeam;
|
|
}
|
|
|
|
async findTeamWithMembers(teamId: number) {
|
|
return await this.prismaClient.team.findUnique({
|
|
where: { id: teamId },
|
|
select: {
|
|
members: {
|
|
select: {
|
|
accepted: true,
|
|
},
|
|
},
|
|
id: true,
|
|
metadata: true,
|
|
parentId: true,
|
|
isOrganization: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
async findTeamsByUserId({ userId, includeOrgs }: { userId: number; includeOrgs?: boolean }) {
|
|
const memberships = await this.prismaClient.membership.findMany({
|
|
where: {
|
|
// Show all the teams this user belongs to regardless of the team being part of the user's org or not
|
|
// We don't want to restrict in the listing here. If we need to restrict a situation where a user is part of the org along with being part of a non-org team, we should do that instead of filtering out from here
|
|
// This became necessary when we started migrating user to Org, without migrating some teams of the user to the org
|
|
// Also, we would allow a user to be part of multiple orgs, then also it would be necessary.
|
|
userId: userId,
|
|
},
|
|
include: {
|
|
team: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
slug: true,
|
|
logoUrl: true,
|
|
isOrganization: true,
|
|
metadata: true,
|
|
inviteTokens: true,
|
|
parent: true,
|
|
parentId: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: { role: "desc" },
|
|
});
|
|
|
|
return memberships
|
|
.filter((mmship) => {
|
|
if (includeOrgs) return true;
|
|
return !mmship.team.isOrganization;
|
|
})
|
|
.map(({ team: { inviteTokens, ...team }, ...membership }) => ({
|
|
role: membership.role,
|
|
accepted: membership.accepted,
|
|
...team,
|
|
metadata: teamMetadataSchema.parse(team.metadata),
|
|
/** To prevent breaking we only return non-email attached token here, if we have one */
|
|
inviteToken: inviteTokens.find((token) => token.identifier === `invite-link-for-teamId-${team.id}`),
|
|
}));
|
|
}
|
|
|
|
async findTeamWithOrganizationSettings(teamId: number) {
|
|
return await this.prismaClient.team.findUnique({
|
|
where: { id: teamId },
|
|
select: {
|
|
parent: {
|
|
select: {
|
|
isOrganization: true,
|
|
organizationSettings: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
async findParentOrganizationByTeamId(teamId: number) {
|
|
const team = await this.prismaClient.team.findUnique({
|
|
where: {
|
|
id: teamId,
|
|
},
|
|
select: {
|
|
parent: {
|
|
select: {
|
|
id: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return team?.parent;
|
|
}
|
|
|
|
async findOrganizationSettingsBySlug({ slug }: { slug: string }) {
|
|
return await this.prismaClient.team.findFirst({
|
|
where: {
|
|
slug,
|
|
isOrganization: true,
|
|
},
|
|
select: {
|
|
organizationSettings: {
|
|
select: {
|
|
adminGetsNoSlotsNotification: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
async findTeamSlugById({ id }: { id: number }) {
|
|
return await this.prismaClient.team.findUnique({
|
|
where: {
|
|
id,
|
|
},
|
|
select: {
|
|
slug: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
async getTeamByIdIfUserIsAdmin({ userId, teamId }: { userId: number; teamId: number }) {
|
|
return await this.prismaClient.team.findUnique({
|
|
where: {
|
|
id: teamId,
|
|
},
|
|
select: {
|
|
id: true,
|
|
metadata: true,
|
|
members: {
|
|
where: {
|
|
userId,
|
|
role: {
|
|
in: [MembershipRole.ADMIN, MembershipRole.OWNER],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
async findTeamWithParentHideBranding({ teamId }: { teamId: number }) {
|
|
return await this.prismaClient.team.findUnique({
|
|
where: { id: teamId },
|
|
select: {
|
|
hideBranding: true,
|
|
parent: {
|
|
select: {
|
|
hideBranding: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
async findOrganization({ teamId, userId }: { teamId?: number; userId: number }) {
|
|
return await this.prismaClient.team.findFirst({
|
|
where: {
|
|
isOrganization: true,
|
|
children: {
|
|
some: {
|
|
id: teamId,
|
|
},
|
|
},
|
|
members: {
|
|
some: {
|
|
userId,
|
|
accepted: true,
|
|
},
|
|
},
|
|
},
|
|
select: {
|
|
id: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
async isSlugAvailableForUpdate({
|
|
slug,
|
|
teamId,
|
|
parentId,
|
|
}: {
|
|
slug: string;
|
|
teamId: number;
|
|
parentId?: number | null;
|
|
}) {
|
|
const whereClause: Prisma.TeamWhereInput = {
|
|
slug: {
|
|
equals: slug,
|
|
mode: "insensitive",
|
|
},
|
|
parentId: parentId ?? null,
|
|
NOT: { id: teamId },
|
|
};
|
|
|
|
const conflictingTeam = await this.prismaClient.team.findFirst({
|
|
where: whereClause,
|
|
select: { id: true },
|
|
});
|
|
|
|
return !conflictingTeam;
|
|
}
|
|
|
|
async findOrgTeamsExcludingTeam({ parentId, excludeTeamId }: { parentId: number; excludeTeamId: number }) {
|
|
return await this.prismaClient.team.findMany({
|
|
where: {
|
|
parentId,
|
|
id: {
|
|
not: excludeTeamId,
|
|
},
|
|
},
|
|
select: { id: true },
|
|
});
|
|
}
|
|
}
|