mirror of
https://github.com/hanzoai/calendar.git
synced 2026-10-30 14:46:43 +00:00
* feat: redirect to new onboarding flow * Getting started * Brand details * Preview organization brands * Orgs team pages * Invite team steps * Move to global zustand store * Few darkmdoe fixes * Wip onboarding + stripe flow * Default plan state Server Action for gettting slug satus of org * Remove onboardingId * Confirmation prompt * Update old onboarding flow handlers to handle new fields * update onboarding hook * Filter out organization section for none -company emails * Match placeholders to users domain * Drop migration * Wip new onboarding intent * WIP flow for self-hosted. Same service call just split logic * WIP * Add TODO * Use onboarding user type instead of trpc session * WIP * WIP * pass role and team name from onboarding to save in schema * Add test to ensure role + name + team are persisted into onboarding table * migrate roles to enum values * Update ENUM * Fix type error * Redirect if flag is disabled * Remove web * WIP * WIP * Fix migration * Fix calls * User onboarding User types instead of trpc session * Fix factory tests * Fix flow for self hoste * Type error * More type fixes * Fix handler tests * Fix enum return type being different * Use consistant types across the oganization stuff * Fix * Use TEAM_BILLING for e2e test * Refactor is not company email and add tests * Fix * Fix * Refactor flow to submit after form complete * Fix flow with billing disabled * Fix tests * Apply suggestion from @coderabbitai[bot] Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Rename and move test files * WIP * Fix types * Update repo paths + tests * Move to service folder * Fix tests * Fix types * Remove old test files * Restore lock * Fix path * Fix tests with new paths and factory logic * Fix updaetdAt * WIP onboardingID isolation * Fix e2e test * verify test * Code rabbit * Rename SelfHostedOnboardongService -> SelfHostedOrganizationOnboardingService * Fix stores * Fix type error * Fix types * remove tsignore * Apply suggestion from @coderabbitai[bot] Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * NITS * Add the logic to auto complete admin org when billing enabled * Fix store being weird * We need to return the parsed value * fixes * sync from db always * Add onboardingSgtore tests * fix test * remove step and status --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Hariom Balhara <[email protected]>
117 lines
4.4 KiB
TypeScript
117 lines
4.4 KiB
TypeScript
import { LicenseKeySingleton } from "@calcom/ee/common/server/LicenseKeyService";
|
|
import { OrganizationOnboardingFactory } from "@calcom/ee/organizations/lib/service/onboarding/OrganizationOnboardingFactory";
|
|
import {
|
|
assertCanCreateOrg,
|
|
findUserToBeOrgOwner,
|
|
} from "@calcom/features/ee/organizations/lib/server/orgCreationUtils";
|
|
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
|
|
import logger from "@calcom/lib/logger";
|
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
|
import { DeploymentRepository } from "@calcom/lib/server/repository/deployment";
|
|
import { OrganizationOnboardingRepository } from "@calcom/lib/server/repository/organizationOnboarding";
|
|
import { prisma } from "@calcom/prisma";
|
|
import { UserPermissionRole } from "@calcom/prisma/enums";
|
|
|
|
import { TRPCError } from "@trpc/server";
|
|
|
|
import type { TrpcSessionUser } from "../../../types";
|
|
import type { TIntentToCreateOrgInputSchema } from "./intentToCreateOrg.schema";
|
|
|
|
const log = logger.getSubLogger({ prefix: ["intentToCreateOrgHandler"] });
|
|
|
|
type CreateOptions = {
|
|
ctx: {
|
|
user: NonNullable<TrpcSessionUser>;
|
|
};
|
|
input: TIntentToCreateOrgInputSchema;
|
|
};
|
|
|
|
export const intentToCreateOrgHandler = async ({ input, ctx }: CreateOptions) => {
|
|
const { slug, name, orgOwnerEmail, isPlatform } = input;
|
|
log.debug(
|
|
"Starting organization creation intent",
|
|
safeStringify({ slug, name, orgOwnerEmail, isPlatform })
|
|
);
|
|
|
|
if (IS_SELF_HOSTED) {
|
|
const deploymentRepo = new DeploymentRepository(prisma);
|
|
const licenseKeyService = await LicenseKeySingleton.getInstance(deploymentRepo);
|
|
const hasValidLicense = await licenseKeyService.checkLicense();
|
|
|
|
if (!hasValidLicense) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "License is not valid",
|
|
});
|
|
}
|
|
}
|
|
|
|
const loggedInUser = ctx.user;
|
|
if (!loggedInUser) throw new TRPCError({ code: "UNAUTHORIZED", message: "You are not authorized." });
|
|
|
|
const IS_USER_ADMIN = loggedInUser.role === UserPermissionRole.ADMIN;
|
|
log.debug("User authorization check", safeStringify({ userId: loggedInUser.id, isAdmin: IS_USER_ADMIN }));
|
|
|
|
if (!IS_USER_ADMIN && loggedInUser.email !== orgOwnerEmail && !isPlatform) {
|
|
log.warn(
|
|
"Unauthorized organization creation attempt",
|
|
safeStringify({ loggedInUserEmail: loggedInUser.email, orgOwnerEmail })
|
|
);
|
|
throw new TRPCError({
|
|
code: "FORBIDDEN",
|
|
message: "You can only create organization where you are the owner",
|
|
});
|
|
}
|
|
|
|
const orgOwner = await findUserToBeOrgOwner(orgOwnerEmail);
|
|
if (!orgOwner) {
|
|
// The flow exists to create the user through the stripe webhook invoice.paid but there could be a possible security issue with the approach. So, we avoid it currently.
|
|
// Issue: As the onboarding link(which has onboardingId) could be used by unwanted person to pay and then invite some unwanted members to the organization.
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: `No user found with email ${orgOwnerEmail}`,
|
|
});
|
|
}
|
|
log.debug("Found organization owner", safeStringify({ orgOwnerId: orgOwner.id, email: orgOwner.email }));
|
|
|
|
const organizationOnboarding = await OrganizationOnboardingRepository.findByOrgOwnerEmail(orgOwner.email);
|
|
|
|
// If onboarding exists and is incomplete, this is a resume flow (e.g., admin handover)
|
|
// Allow proceeding with the existing onboarding record
|
|
if (organizationOnboarding) {
|
|
if (organizationOnboarding.isComplete) {
|
|
// Organization already created - shouldn't create another one
|
|
throw new Error("organization_onboarding_already_exists");
|
|
}
|
|
|
|
// Incomplete onboarding exists - this is expected for resume/handover flows
|
|
log.debug(
|
|
"Found incomplete onboarding record - proceeding with resume flow",
|
|
safeStringify({ onboardingId: organizationOnboarding.id, slug })
|
|
);
|
|
|
|
// Use existing onboarding ID for the resume flow
|
|
input.onboardingId = organizationOnboarding.id;
|
|
}
|
|
|
|
await assertCanCreateOrg({
|
|
slug,
|
|
isPlatform,
|
|
orgOwner,
|
|
restrictBasedOnMinimumPublishedTeams: !IS_USER_ADMIN,
|
|
});
|
|
|
|
const onboardingService = OrganizationOnboardingFactory.create({
|
|
id: ctx.user.id,
|
|
email: ctx.user.email,
|
|
role: ctx.user.role,
|
|
});
|
|
const result = await onboardingService.createOnboardingIntent(input);
|
|
|
|
log.debug("Organization creation intent successful", safeStringify({ slug, orgOwnerId: orgOwner.id }));
|
|
|
|
return result;
|
|
};
|
|
|
|
export default intentToCreateOrgHandler;
|