var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { IsBillingEnabled, NotificationSlackWebhookOnCreateProject, NotificationSlackWebhookOnDeleteProject, NotificationSlackWebhookOnSubscriptionUpdate, getAllEnvVars, } from "../EnvironmentConfig";
import AllMeteredPlans from "../Types/Billing/MeteredPlan/AllMeteredPlans";
import QueryHelper from "../Types/Database/QueryHelper";
import logger from "../Utils/Logger";
import Errors from "../Utils/Errors";
import AccessTokenService from "./AccessTokenService";
import BillingService from "./BillingService";
import DatabaseService from "./DatabaseService";
import IncidentSeverityService from "./IncidentSeverityService";
import IncidentStateService from "./IncidentStateService";
import IncidentRoleService from "./IncidentRoleService";
import MailService from "./MailService";
import MonitorStatusService from "./MonitorStatusService";
import NotificationService from "./NotificationService";
import PromoCodeService from "./PromoCodeService";
import ScheduledMaintenanceStateService from "./ScheduledMaintenanceStateService";
import TeamMemberService from "./TeamMemberService";
import TeamPermissionService from "./TeamPermissionService";
import TeamService from "./TeamService";
import UserNotificationRuleService from "./UserNotificationRuleService";
import UserNotificationSettingService from "./UserNotificationSettingService";
import UserService from "./UserService";
import SubscriptionPlan from "../../Types/Billing/SubscriptionPlan";
import SubscriptionStatus from "../../Types/Billing/SubscriptionStatus";
import { Black, Blue500, Gray500, Green, Moroon500, Purple500, Red, Teal500, Yellow, Yellow500, } from "../../Types/BrandColors";
import Color from "../../Types/Color";
import LIMIT_MAX from "../../Types/Database/LimitMax";
import OneUptimeDate from "../../Types/Date";
import EmailTemplateType from "../../Types/Email/EmailTemplateType";
import BadDataException from "../../Types/Exception/BadDataException";
import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
import IconProp from "../../Types/Icon/IconProp";
import ObjectID from "../../Types/ObjectID";
import Permission from "../../Types/Permission";
import IncidentSeverity from "../../Models/DatabaseModels/IncidentSeverity";
import IncidentState from "../../Models/DatabaseModels/IncidentState";
import IncidentRole from "../../Models/DatabaseModels/IncidentRole";
import MonitorStatus from "../../Models/DatabaseModels/MonitorStatus";
import Model from "../../Models/DatabaseModels/Project";
import ScheduledMaintenanceState from "../../Models/DatabaseModels/ScheduledMaintenanceState";
import Team from "../../Models/DatabaseModels/Team";
import TeamMember from "../../Models/DatabaseModels/TeamMember";
import TeamPermission from "../../Models/DatabaseModels/TeamPermission";
import AlertSeverity from "../../Models/DatabaseModels/AlertSeverity";
import AlertSeverityService from "./AlertSeverityService";
import AlertState from "../../Models/DatabaseModels/AlertState";
import AlertStateService from "./AlertStateService";
import SlackUtil from "../Utils/Workspace/Slack/Slack";
import URL from "../../Types/API/URL";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import DatabaseConfig from "../DatabaseConfig";
import Semaphore from "../Infrastructure/Semaphore";
import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
export class ProjectService extends DatabaseService {
    constructor() {
        super(Model);
        /*
         * Suppresses repeated `lastActive` UPDATEs from a single API node. 60s of
         * staleness on "last seen" is acceptable; an UPDATE per request is not.
         */
        this.lastActiveCache = new InMemoryTTLCache(10000);
        /*
         * Caches the `requireSsoForLogin` flag per project so middleware can skip a
         * Postgres findOneById on every authenticated request.
         */
        this.requireSsoForLoginCache = new InMemoryTTLCache(10000);
        /*
         * Caches the current billing plan per project. `getCurrentPlan` is hit
         * by `CommonAPI.getDatabaseCommonInteractionProps` on every
         * authenticated request when billing is enabled — without caching,
         * that's one Postgres findOneById per API call to a billable project.
         * Plans change rarely (subscription create / cancel / change), so a
         * 60s staleness window is acceptable.
         */
        this.currentPlanCache = new InMemoryTTLCache(10000);
    }
    getPlanType(planId) {
        if (!SubscriptionPlan.isValidPlanId(planId, getAllEnvVars())) {
            throw new BadDataException("Plan is invalid.");
        }
        return SubscriptionPlan.getPlanType(planId);
    }
    async onBeforeCreate(data) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j;
        if (!data.data.name) {
            throw new BadDataException("Project name is required");
        }
        if (data.props.userId) {
            data.data.createdByUserId = data.props.userId;
        }
        else {
            throw new NotAuthorizedException("User should be logged in to create the project.");
        }
        logger.debug("Creating project for user " + data.props.userId, {
            userId: (_a = data.props.userId) === null || _a === void 0 ? void 0 : _a.toString(),
        });
        const user = await UserService.findOneById({
            id: data.props.userId,
            select: {
                name: true,
                email: true,
                isMasterAdmin: true,
                companyPhoneNumber: true,
                companyName: true,
                utmCampaign: true,
                utmSource: true,
                utmMedium: true,
                utmTerm: true,
                utmContent: true,
                utmUrl: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!user) {
            throw new BadDataException("User not found.");
        }
        // Check if project creation is restricted to admins only
        const shouldDisableProjectCreation = await DatabaseConfig.shouldDisableUserProjectCreation();
        if (shouldDisableProjectCreation && !user.isMasterAdmin) {
            throw new NotAuthorizedException("Project creation is restricted to admin users only on this OneUptime Server. Please contact your server admin.");
        }
        if (IsBillingEnabled) {
            if (!data.data.paymentProviderPlanId) {
                throw new BadDataException("Plan required to create the project.");
            }
            data.data.planName = this.getPlanType(data.data.paymentProviderPlanId);
            if (data.data.paymentProviderPromoCode) {
                /*
                 * check if it exists in promcode table. Not all promocodes are in the table, only reseller ones are.
                 * If they are not in the table, allow projetc creation to proceed.
                 * If they are in the project table, then see if anyn restrictions on reseller plan apply and if it does,
                 * apply those restictions to the project.
                 */
                const promoCode = await PromoCodeService.findOneBy({
                    query: {
                        promoCodeId: data.data.paymentProviderPromoCode,
                    },
                    select: {
                        isPromoCodeUsed: true,
                        userEmail: true,
                        resellerPlan: {
                            _id: true,
                            planType: true,
                            monitorLimit: true,
                            teamMemberLimit: true,
                        },
                        resellerId: true,
                        resellerLicenseId: true,
                        planType: true,
                        resellerPlanId: true,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                if (promoCode) {
                    // check if the same user is creating the project.
                    if (((_b = promoCode.userEmail) === null || _b === void 0 ? void 0 : _b.toString()) !== ((_c = user.email) === null || _c === void 0 ? void 0 : _c.toString())) {
                        throw new BadDataException("This promocode is assigned to a different user and cannot be used.");
                    }
                    if (promoCode.isPromoCodeUsed) {
                        throw new BadDataException("This promocode has already been used.");
                    }
                    if ((_d = promoCode.resellerPlan) === null || _d === void 0 ? void 0 : _d.monitorLimit) {
                        data.data.activeMonitorsLimit =
                            (_e = promoCode.resellerPlan) === null || _e === void 0 ? void 0 : _e.monitorLimit;
                    }
                    if ((_f = promoCode.resellerPlan) === null || _f === void 0 ? void 0 : _f.teamMemberLimit) {
                        data.data.seatLimit = (_g = promoCode.resellerPlan) === null || _g === void 0 ? void 0 : _g.teamMemberLimit;
                    }
                    if (promoCode.planType !== data.data.planName) {
                        throw new BadDataException("Promocode is not valid for this plan. Please select the " +
                            promoCode.planType +
                            " plan.");
                    }
                    if (promoCode.resellerLicenseId) {
                        data.data.resellerLicenseId = promoCode.resellerLicenseId;
                    }
                    if (promoCode.resellerId) {
                        data.data.resellerId = promoCode.resellerId;
                    }
                    if (promoCode.resellerPlanId) {
                        data.data.resellerPlanId = promoCode.resellerPlanId;
                    }
                }
            }
            if (data.data.paymentProviderPromoCode &&
                !(await BillingService.isPromoCodeValid(data.data.paymentProviderPromoCode))) {
                throw new BadDataException("Promo code is invalid.");
            }
            // check if promocode is valid.
        }
        // check if the user has the project with the same name. If yes, reject.
        let existingProjectWithSameNameCount = 0;
        if (data.props.userGlobalAccessPermission &&
            ((_h = data.props.userGlobalAccessPermission) === null || _h === void 0 ? void 0 : _h.projectIds.length) > 0) {
            existingProjectWithSameNameCount = (await this.countBy({
                query: {
                    _id: QueryHelper.any(((_j = data.props.userGlobalAccessPermission) === null || _j === void 0 ? void 0 : _j.projectIds.map((item) => {
                        return item.toString();
                    })) || []),
                    name: QueryHelper.findWithSameText(data.data.name),
                },
                props: {
                    isRoot: true,
                },
            })).toNumber();
        }
        if (existingProjectWithSameNameCount > 0) {
            throw new BadDataException("Project with the same name already exists");
        }
        data.data.createdOwnerName = user.name;
        data.data.createdOwnerEmail = user.email;
        data.data.createdOwnerPhone = user.companyPhoneNumber;
        data.data.createdOwnerCompanyName = user.companyName;
        // UTM info.
        data.data.utmCampaign = user.utmCampaign;
        data.data.utmSource = user.utmSource;
        data.data.utmMedium = user.utmMedium;
        data.data.utmTerm = user.utmTerm;
        data.data.utmContent = user.utmContent;
        data.data.utmUrl = user.utmUrl;
        // Set default number prefixes.
        if (!data.data.incidentNumberPrefix) {
            data.data.incidentNumberPrefix = "INC-";
        }
        if (!data.data.alertNumberPrefix) {
            data.data.alertNumberPrefix = "ALT-";
        }
        if (!data.data.scheduledMaintenanceNumberPrefix) {
            data.data.scheduledMaintenanceNumberPrefix = "SM-";
        }
        if (!data.data.incidentEpisodeNumberPrefix) {
            data.data.incidentEpisodeNumberPrefix = "IE-";
        }
        if (!data.data.alertEpisodeNumberPrefix) {
            data.data.alertEpisodeNumberPrefix = "AE-";
        }
        return Promise.resolve({ createBy: data, carryForward: null });
    }
    async onBeforeUpdate(updateBy) {
        var _a;
        /*
         * Any project field could have changed; invalidate the in-process cache
         * of the SSO flag. Cheap to refetch on the next request.
         */
        if (updateBy.data.requireSsoForLogin !== undefined) {
            this.requireSsoForLoginCache.clear();
        }
        if (IsBillingEnabled) {
            if (updateBy.data.businessDetails ||
                updateBy.data.businessDetailsCountry ||
                updateBy.data.financeAccountingEmail ||
                updateBy.data.sendInvoicesByEmail !== undefined) {
                logger.debug(`[Invoice Email] ProjectService.onBeforeUpdate - syncing billing details to Stripe`);
                logger.debug(`[Invoice Email] Fields being updated - businessDetails: ${Boolean(updateBy.data.businessDetails)}, businessDetailsCountry: ${Boolean(updateBy.data.businessDetailsCountry)}, financeAccountingEmail: ${Boolean(updateBy.data.financeAccountingEmail)}, sendInvoicesByEmail: ${updateBy.data.sendInvoicesByEmail}`);
                // Sync to Stripe.
                const project = await this.findOneById({
                    id: new ObjectID(updateBy.query._id),
                    select: {
                        paymentProviderCustomerId: true,
                        financeAccountingEmail: true,
                        sendInvoicesByEmail: true,
                    },
                    props: { isRoot: true },
                });
                logger.debug(`[Invoice Email] Project found - paymentProviderCustomerId: ${project === null || project === void 0 ? void 0 : project.paymentProviderCustomerId}, existing sendInvoicesByEmail: ${project === null || project === void 0 ? void 0 : project.sendInvoicesByEmail}`);
                if (project === null || project === void 0 ? void 0 : project.paymentProviderCustomerId) {
                    try {
                        const sendInvoicesByEmailValue = updateBy.data.sendInvoicesByEmail !== undefined
                            ? updateBy.data.sendInvoicesByEmail
                            : project.sendInvoicesByEmail || null;
                        logger.debug(`[Invoice Email] Calling BillingService.updateCustomerBusinessDetails with sendInvoicesByEmail: ${sendInvoicesByEmailValue}`);
                        await BillingService.updateCustomerBusinessDetails(project.paymentProviderCustomerId, updateBy.data.businessDetails || "", updateBy.data.businessDetailsCountry || null, updateBy.data.financeAccountingEmail ||
                            project.financeAccountingEmail ||
                            null, sendInvoicesByEmailValue);
                        logger.debug(`[Invoice Email] Successfully synced billing details to Stripe for customer ${project.paymentProviderCustomerId}`);
                    }
                    catch (err) {
                        logger.error(`[Invoice Email] Failed to update Stripe customer business details: ${err}`, { projectId: (_a = updateBy.query._id) === null || _a === void 0 ? void 0 : _a.toString() });
                    }
                }
                else {
                    logger.debug(`[Invoice Email] No paymentProviderCustomerId found, skipping Stripe sync`);
                }
            }
            if (updateBy.data.enableAutoRechargeSmsOrCallBalance) {
                await NotificationService.rechargeIfBalanceIsLow(new ObjectID(updateBy.query._id), {
                    autoRechargeSmsOrCallByBalanceInUSD: updateBy.data
                        .autoRechargeSmsOrCallByBalanceInUSD,
                    autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: updateBy.data
                        .autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD,
                    enableAutoRechargeSmsOrCallBalance: updateBy.data
                        .enableAutoRechargeSmsOrCallBalance,
                });
            }
            if (updateBy.data.paymentProviderPlanId &&
                !updateBy.props.ignoreHooks &&
                !updateBy.props.isRoot) {
                throw new BadDataException("Project plan cannot be updated directly. Please use the change plan API.");
            }
        }
        return { updateBy, carryForward: [] };
    }
    async changePlan(params) {
        var _a, _b;
        if (!IsBillingEnabled) {
            throw new BadDataException("Billing is not enabled for this server");
        }
        const project = await this.findOneById({
            id: params.projectId,
            select: {
                _id: true,
                paymentProviderSubscriptionId: true,
                paymentProviderMeteredSubscriptionId: true,
                paymentProviderSubscriptionSeats: true,
                paymentProviderPlanId: true,
                trialEndsAt: true,
                paymentProviderCustomerId: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!project) {
            throw new BadDataException("Project not found");
        }
        if (!project.paymentProviderSubscriptionId) {
            throw new BadDataException("Payment Provider subscription not found");
        }
        if (!project.paymentProviderMeteredSubscriptionId) {
            throw new BadDataException("Payment Provider metered subscription not found");
        }
        // Check if customer has payment methods before attempting to change plan
        if (!project.paymentProviderCustomerId) {
            throw new BadDataException("Payment Provider customer not found");
        }
        const hasPaymentMethods = await BillingService.hasPaymentMethods(project.paymentProviderCustomerId);
        if (!hasPaymentMethods) {
            throw new BadDataException(Errors.BillingService.NO_PAYMENTS_METHODS);
        }
        const plan = SubscriptionPlan.getSubscriptionPlanById(params.paymentProviderPlanId, getAllEnvVars());
        if (!plan) {
            throw new BadDataException("Invalid plan");
        }
        let seats = project.paymentProviderSubscriptionSeats;
        if (!seats || seats <= 0) {
            seats = await TeamMemberService.getUniqueTeamMemberCountInProject(project.id);
        }
        logger.debug(`Changing plan for project ${(_a = project.id) === null || _a === void 0 ? void 0 : _a.toString()} to ${plan.getName()} with seats ${seats}`, { projectId: (_b = project.id) === null || _b === void 0 ? void 0 : _b.toString() });
        const endTrialAt = params.endTrialAt !== undefined
            ? params.endTrialAt || undefined
            : project.trialEndsAt || undefined;
        const subscription = await BillingService.changePlan({
            projectId: project.id,
            subscriptionId: project.paymentProviderSubscriptionId,
            meteredSubscriptionId: project.paymentProviderMeteredSubscriptionId,
            serverMeteredPlans: AllMeteredPlans,
            newPlan: plan,
            quantity: seats,
            isYearly: plan.getYearlyPlanId() === params.paymentProviderPlanId,
            endTrialAt: endTrialAt,
        });
        const subscriptionState = await BillingService.getSubscriptionStatus(subscription.subscriptionId);
        const meteredSubscriptionState = await BillingService.getSubscriptionStatus(subscription.meteredSubscriptionId);
        await this.updateOneById({
            id: project.id,
            data: {
                paymentProviderPlanId: params.paymentProviderPlanId,
                paymentProviderSubscriptionId: subscription.subscriptionId,
                paymentProviderMeteredSubscriptionId: subscription.meteredSubscriptionId,
                paymentProviderSubscriptionSeats: seats,
                trialEndsAt: subscription.trialEndsAt || endTrialAt || new Date(),
                planName: SubscriptionPlan.getPlanType(params.paymentProviderPlanId, getAllEnvVars()),
                paymentProviderMeteredSubscriptionStatus: meteredSubscriptionState,
                paymentProviderSubscriptionStatus: subscriptionState,
            },
            props: {
                isRoot: true,
                ignoreHooks: true,
            },
        });
        await this.sendSubscriptionChangeWebhookSlackNotification(project.id);
    }
    async sendSubscriptionChangeWebhookSlackNotification(projectId) {
        var _a, _b, _c, _d, _e;
        if (NotificationSlackWebhookOnSubscriptionUpdate) {
            // fetch project again.
            const project = await this.findOneById({
                id: new ObjectID(projectId.toString()),
                select: {
                    name: true,
                    _id: true,
                    createdOwnerName: true,
                    createdOwnerEmail: true,
                    planName: true,
                    createdByUserId: true,
                    paymentProviderSubscriptionStatus: true,
                },
                props: {
                    isRoot: true,
                },
            });
            if (!project) {
                throw new BadDataException("Project not found");
            }
            let slackMessage = `*Project Plan Changed:*
*Project Name:* ${((_a = project.name) === null || _a === void 0 ? void 0 : _a.toString()) || "N/A"}
*Project ID:* ${((_b = project.id) === null || _b === void 0 ? void 0 : _b.toString()) || "N/A"}
`;
            if (project.createdOwnerName && project.createdOwnerEmail) {
                slackMessage += `*Project Created By:* ${((_c = project === null || project === void 0 ? void 0 : project.createdOwnerName) === null || _c === void 0 ? void 0 : _c.toString()) + " (" + project.createdOwnerEmail.toString() + ")" || "N/A"}
`;
            }
            if (IsBillingEnabled) {
                // which plan?
                slackMessage += `*Plan:* ${((_d = project.planName) === null || _d === void 0 ? void 0 : _d.toString()) || "N/A"} 
*Subscription Status:* ${((_e = project.paymentProviderSubscriptionStatus) === null || _e === void 0 ? void 0 : _e.toString()) || "N/A"}
`;
            }
            SlackUtil.sendMessageToChannelViaIncomingWebhook({
                url: URL.fromString(NotificationSlackWebhookOnSubscriptionUpdate),
                text: slackMessage,
            }).catch((error) => {
                var _a;
                logger.error("Error sending slack message: " + error, {
                    projectId: (_a = project === null || project === void 0 ? void 0 : project.id) === null || _a === void 0 ? void 0 : _a.toString(),
                });
            });
        }
    }
    async addDefaultScheduledMaintenanceState(createdItem) {
        let createdScheduledMaintenanceState = new ScheduledMaintenanceState();
        createdScheduledMaintenanceState.name = "Scheduled";
        createdScheduledMaintenanceState.description =
            "When an event is scheduled, it belongs to this state";
        createdScheduledMaintenanceState.color = Black;
        createdScheduledMaintenanceState.isScheduledState = true;
        createdScheduledMaintenanceState.projectId = createdItem.id;
        createdScheduledMaintenanceState.order = 1;
        createdScheduledMaintenanceState =
            await ScheduledMaintenanceStateService.create({
                data: createdScheduledMaintenanceState,
                props: {
                    isRoot: true,
                },
            });
        let ongoingScheduledMaintenanceState = new ScheduledMaintenanceState();
        ongoingScheduledMaintenanceState.name = "Ongoing";
        ongoingScheduledMaintenanceState.description =
            "When an event is ongoing, it belongs to this state.";
        ongoingScheduledMaintenanceState.color = Yellow;
        ongoingScheduledMaintenanceState.isOngoingState = true;
        ongoingScheduledMaintenanceState.projectId = createdItem.id;
        ongoingScheduledMaintenanceState.order = 2;
        ongoingScheduledMaintenanceState =
            await ScheduledMaintenanceStateService.create({
                data: ongoingScheduledMaintenanceState,
                props: {
                    isRoot: true,
                },
            });
        let endedScheduledMaintenanceState = new ScheduledMaintenanceState();
        endedScheduledMaintenanceState.name = "Ended";
        endedScheduledMaintenanceState.description =
            "Scheduled maintenance events switch to this state when they end.";
        endedScheduledMaintenanceState.color = new Color("#4A4A4A");
        endedScheduledMaintenanceState.isEndedState = true;
        endedScheduledMaintenanceState.projectId = createdItem.id;
        endedScheduledMaintenanceState.order = 3;
        endedScheduledMaintenanceState =
            await ScheduledMaintenanceStateService.create({
                data: endedScheduledMaintenanceState,
                props: {
                    isRoot: true,
                },
            });
        let completedScheduledMaintenanceState = new ScheduledMaintenanceState();
        completedScheduledMaintenanceState.name = "Completed";
        completedScheduledMaintenanceState.description =
            "When an event is completed, it belongs to this state.";
        completedScheduledMaintenanceState.color = Green;
        completedScheduledMaintenanceState.isResolvedState = true;
        completedScheduledMaintenanceState.projectId = createdItem.id;
        completedScheduledMaintenanceState.order = 4;
        completedScheduledMaintenanceState =
            await ScheduledMaintenanceStateService.create({
                data: completedScheduledMaintenanceState,
                props: {
                    isRoot: true,
                },
            });
        return createdItem;
    }
    async onCreateSuccess(_onCreate, createdItem) {
        // Create billing.
        var _a, _b, _c, _d, _e;
        if (IsBillingEnabled) {
            const customerId = await BillingService.createCustomer({
                name: createdItem.name,
                email: createdItem.createdOwnerEmail,
                id: createdItem.id,
            });
            const plan = SubscriptionPlan.getSubscriptionPlanById(createdItem.paymentProviderPlanId, getAllEnvVars());
            if (!plan) {
                throw new BadDataException("Invalid plan.");
            }
            // add subscription to this customer.
            const { subscriptionId, meteredSubscriptionId, trialEndsAt } = await BillingService.subscribeToPlan({
                projectId: createdItem.id,
                customerId,
                serverMeteredPlans: AllMeteredPlans,
                plan,
                quantity: 1,
                isYearly: plan.getYearlyPlanId() === createdItem.paymentProviderPlanId,
                trial: true,
                promoCode: createdItem.paymentProviderPromoCode,
            });
            await this.updateOneById({
                id: createdItem.id,
                data: {
                    paymentProviderCustomerId: customerId,
                    paymentProviderSubscriptionId: subscriptionId,
                    paymentProviderMeteredSubscriptionId: meteredSubscriptionId,
                    paymentProviderSubscriptionSeats: 1,
                    trialEndsAt: (trialEndsAt || null),
                },
                props: {
                    isRoot: true,
                },
            });
            // mark the promo code as used it it exists.
            if (createdItem.paymentProviderPromoCode) {
                await PromoCodeService.updateOneBy({
                    query: {
                        promoCodeId: createdItem.paymentProviderPromoCode,
                    },
                    data: {
                        isPromoCodeUsed: true,
                        promoCodeUsedAt: OneUptimeDate.getCurrentDate(),
                        projectId: createdItem.id,
                    },
                    props: {
                        isRoot: true,
                    },
                });
            }
        }
        /*
         * Each addDefault* method only reads `createdItem.id` and writes rows to a
         * distinct table; none of them mutate `createdItem`. Running them in
         * parallel cuts the onCreate hook latency from sum-of-8 sequential DB
         * round-trips to max-of-8, which removes ~hundreds of ms on project
         * create.
         */
        await Promise.all([
            this.addDefaultIncidentSeverity(createdItem),
            this.addDefaultAlertSeverity(createdItem),
            this.addDefaultProjectTeams(createdItem),
            this.addDefaultMonitorStatus(createdItem),
            this.addDefaultIncidentState(createdItem),
            this.addDefaultScheduledMaintenanceState(createdItem),
            this.addDefaultAlertState(createdItem),
            this.addDefaultIncidentRoles(createdItem),
        ]);
        if (NotificationSlackWebhookOnCreateProject) {
            // fetch project again.
            const project = await this.findOneById({
                id: createdItem.id,
                select: {
                    name: true,
                    _id: true,
                    createdOwnerName: true,
                    createdOwnerEmail: true,
                    planName: true,
                    createdByUserId: true,
                    paymentProviderSubscriptionStatus: true,
                },
                props: {
                    isRoot: true,
                },
            });
            if (!project) {
                throw new BadDataException("Project not found");
            }
            let slackMessage = `*Project Created:*
*Project Name:* ${((_a = project.name) === null || _a === void 0 ? void 0 : _a.toString()) || "N/A"}
*Project ID:* ${((_b = project.id) === null || _b === void 0 ? void 0 : _b.toString()) || "N/A"}
`;
            if (project.createdOwnerName && project.createdOwnerEmail) {
                slackMessage += `*Created By:* ${((_c = project === null || project === void 0 ? void 0 : project.createdOwnerName) === null || _c === void 0 ? void 0 : _c.toString()) + " (" + project.createdOwnerEmail.toString() + ")" || "N/A"}
`;
                if (IsBillingEnabled) {
                    // which plan?
                    slackMessage += `*Plan:* ${((_d = project.planName) === null || _d === void 0 ? void 0 : _d.toString()) || "N/A"}
*Subscription Status:* ${((_e = project.paymentProviderSubscriptionStatus) === null || _e === void 0 ? void 0 : _e.toString()) || "N/A"}
`;
                }
                SlackUtil.sendMessageToChannelViaIncomingWebhook({
                    url: URL.fromString(NotificationSlackWebhookOnCreateProject),
                    text: slackMessage,
                }).catch((error) => {
                    var _a;
                    logger.error("Error sending slack message: " + error, {
                        projectId: (_a = createdItem.id) === null || _a === void 0 ? void 0 : _a.toString(),
                    });
                });
            }
        }
        return createdItem;
    }
    async addDefaultIncidentState(createdItem) {
        let createdIncidentState = new IncidentState();
        createdIncidentState.name = "Identified";
        createdIncidentState.description =
            "When an incident is created, it belongs to this state";
        createdIncidentState.color = Red;
        createdIncidentState.isCreatedState = true;
        createdIncidentState.projectId = createdItem.id;
        createdIncidentState.order = 1;
        createdIncidentState = await IncidentStateService.create({
            data: createdIncidentState,
            props: {
                isRoot: true,
            },
        });
        let acknowledgedIncidentState = new IncidentState();
        acknowledgedIncidentState.name = "Acknowledged";
        acknowledgedIncidentState.description =
            "When an incident is acknowledged, it belongs to this state.";
        acknowledgedIncidentState.color = Yellow;
        acknowledgedIncidentState.isAcknowledgedState = true;
        acknowledgedIncidentState.projectId = createdItem.id;
        acknowledgedIncidentState.order = 2;
        acknowledgedIncidentState = await IncidentStateService.create({
            data: acknowledgedIncidentState,
            props: {
                isRoot: true,
            },
        });
        let resolvedIncidentState = new IncidentState();
        resolvedIncidentState.name = "Resolved";
        resolvedIncidentState.description =
            "When an incident is resolved, it belongs to this state.";
        resolvedIncidentState.color = Green;
        resolvedIncidentState.isResolvedState = true;
        resolvedIncidentState.projectId = createdItem.id;
        resolvedIncidentState.order = 3;
        resolvedIncidentState = await IncidentStateService.create({
            data: resolvedIncidentState,
            props: {
                isRoot: true,
            },
        });
        return createdItem;
    }
    async addDefaultAlertState(createdItem) {
        let createdAlertState = new AlertState();
        createdAlertState.name = "Identified";
        createdAlertState.description =
            "When an alert is created, it belongs to this state";
        createdAlertState.color = Red;
        createdAlertState.isCreatedState = true;
        createdAlertState.projectId = createdItem.id;
        createdAlertState.order = 1;
        createdAlertState = await AlertStateService.create({
            data: createdAlertState,
            props: {
                isRoot: true,
            },
        });
        let acknowledgedAlertState = new AlertState();
        acknowledgedAlertState.name = "Acknowledged";
        acknowledgedAlertState.description =
            "When an alert is acknowledged, it belongs to this state.";
        acknowledgedAlertState.color = Yellow;
        acknowledgedAlertState.isAcknowledgedState = true;
        acknowledgedAlertState.projectId = createdItem.id;
        acknowledgedAlertState.order = 2;
        acknowledgedAlertState = await AlertStateService.create({
            data: acknowledgedAlertState,
            props: {
                isRoot: true,
            },
        });
        let resolvedAlertState = new AlertState();
        resolvedAlertState.name = "Resolved";
        resolvedAlertState.description =
            "When an incident is resolved, it belongs to this state.";
        resolvedAlertState.color = Green;
        resolvedAlertState.isResolvedState = true;
        resolvedAlertState.projectId = createdItem.id;
        resolvedAlertState.order = 3;
        resolvedAlertState = await AlertStateService.create({
            data: resolvedAlertState,
            props: {
                isRoot: true,
            },
        });
        return createdItem;
    }
    async addDefaultAlertSeverity(createdItem) {
        let highSeverity = new AlertSeverity();
        highSeverity.name = "High";
        highSeverity.description =
            "Issues causing very high impact to customers. Immediate attention is required.";
        highSeverity.color = Moroon500;
        highSeverity.projectId = createdItem.id;
        highSeverity.order = 1;
        highSeverity = await AlertSeverityService.create({
            data: highSeverity,
            props: {
                isRoot: true,
            },
        });
        let lowSeverity = new AlertSeverity();
        lowSeverity.name = "Low";
        lowSeverity.description = "Issues causing low impact to customers.";
        lowSeverity.color = Yellow500;
        lowSeverity.projectId = createdItem.id;
        lowSeverity.order = 2;
        lowSeverity = await AlertSeverityService.create({
            data: lowSeverity,
            props: {
                isRoot: true,
            },
        });
        return createdItem;
    }
    async addDefaultIncidentSeverity(createdItem) {
        let criticalIncident = new IncidentSeverity();
        criticalIncident.name = "Critical Incident";
        criticalIncident.description =
            "Issues causing very high impact to customers. Immediate response is required. Examples include a full outage, or a data breach.";
        criticalIncident.color = Moroon500;
        criticalIncident.projectId = createdItem.id;
        criticalIncident.order = 1;
        criticalIncident = await IncidentSeverityService.create({
            data: criticalIncident,
            props: {
                isRoot: true,
            },
        });
        let majorIncident = new IncidentSeverity();
        majorIncident.name = "Major Incident";
        majorIncident.description =
            "Issues causing significant impact. Immediate response is usually required. We might have some workarounds that mitigate the impact on customers. Examples include an important sub-system failing.";
        majorIncident.color = Red;
        majorIncident.projectId = createdItem.id;
        majorIncident.order = 2;
        majorIncident = await IncidentSeverityService.create({
            data: majorIncident,
            props: {
                isRoot: true,
            },
        });
        let minorIncident = new IncidentSeverity();
        minorIncident.name = "Minor Incident";
        minorIncident.description =
            "Issues with low impact, which can usually be handled within working hours. Most customers are unlikely to notice any problems. Examples include a slight drop in application performance.";
        minorIncident.color = Yellow;
        minorIncident.projectId = createdItem.id;
        minorIncident.order = 3;
        minorIncident = await IncidentSeverityService.create({
            data: minorIncident,
            props: {
                isRoot: true,
            },
        });
        return createdItem;
    }
    async addDefaultIncidentRoles(createdItem) {
        let incidentCommander = new IncidentRole();
        incidentCommander.name = "Incident Commander";
        incidentCommander.description =
            "Primary decision maker during an incident. Responsible for coordinating the response and making final decisions.";
        incidentCommander.color = Purple500;
        incidentCommander.roleIcon = IconProp.ShieldCheck;
        incidentCommander.projectId = createdItem.id;
        incidentCommander.isPrimaryRole = true;
        incidentCommander.isDeleteable = false;
        incidentCommander = await IncidentRoleService.create({
            data: incidentCommander,
            props: {
                isRoot: true,
            },
        });
        let responder = new IncidentRole();
        responder.name = "Responder";
        responder.description =
            "Active participant in incident resolution. Performs hands-on work to resolve the incident.";
        responder.color = Blue500;
        responder.roleIcon = IconProp.Wrench;
        responder.projectId = createdItem.id;
        responder = await IncidentRoleService.create({
            data: responder,
            props: {
                isRoot: true,
            },
        });
        let communicationsLead = new IncidentRole();
        communicationsLead.name = "Communications Lead";
        communicationsLead.description =
            "Handles stakeholder communication and status updates during an incident.";
        communicationsLead.color = Teal500;
        communicationsLead.roleIcon = IconProp.Announcement;
        communicationsLead.projectId = createdItem.id;
        communicationsLead = await IncidentRoleService.create({
            data: communicationsLead,
            props: {
                isRoot: true,
            },
        });
        let observer = new IncidentRole();
        observer.name = "Observer";
        observer.description =
            "Read-only participant who monitors the incident without active involvement.";
        observer.color = Gray500;
        observer.roleIcon = IconProp.Activity;
        observer.projectId = createdItem.id;
        observer.canAssignMultipleUsers = true;
        observer = await IncidentRoleService.create({
            data: observer,
            props: {
                isRoot: true,
            },
        });
        return createdItem;
    }
    async addDefaultMonitorStatus(createdItem) {
        let operationalStatus = new MonitorStatus();
        operationalStatus.name = "Operational";
        operationalStatus.description = "Monitor operating normally";
        operationalStatus.projectId = createdItem.id;
        operationalStatus.priority = 1;
        operationalStatus.isOperationalState = true;
        operationalStatus.color = Green;
        operationalStatus = await MonitorStatusService.create({
            data: operationalStatus,
            props: {
                isRoot: true,
            },
        });
        let degradedStatus = new MonitorStatus();
        degradedStatus.name = "Degraded";
        degradedStatus.description = "Monitor is operating at reduced performance.";
        degradedStatus.priority = 2;
        degradedStatus.projectId = createdItem.id;
        degradedStatus.color = Yellow;
        degradedStatus = await MonitorStatusService.create({
            data: degradedStatus,
            props: {
                isRoot: true,
            },
        });
        let downStatus = new MonitorStatus();
        downStatus.name = "Offline";
        downStatus.description = "Monitor is offline.";
        downStatus.isOfflineState = true;
        downStatus.projectId = createdItem.id;
        downStatus.priority = 3;
        downStatus.color = Red;
        downStatus = await MonitorStatusService.create({
            data: downStatus,
            props: {
                isRoot: true,
            },
        });
        return createdItem;
    }
    async addDefaultProjectTeams(createdItem) {
        // add a team member.
        // Owner Team.
        let ownerTeam = new Team();
        ownerTeam.projectId = createdItem.id;
        ownerTeam.name = "Owners";
        ownerTeam.shouldHaveAtLeastOneMember = true;
        ownerTeam.isPermissionsEditable = false;
        ownerTeam.isTeamEditable = false;
        ownerTeam.isTeamDeleteable = false;
        ownerTeam.description =
            "This team is for project owners. Adding team members to this team will give them root level permissions.";
        ownerTeam = await TeamService.create({
            data: ownerTeam,
            props: {
                isRoot: true,
            },
        });
        // Add current user to owners team.
        let ownerTeamMember = new TeamMember();
        ownerTeamMember.projectId = createdItem.id;
        ownerTeamMember.userId = createdItem.createdByUserId;
        ownerTeamMember.hasAcceptedInvitation = true;
        ownerTeamMember.invitationAcceptedAt = OneUptimeDate.getCurrentDate();
        ownerTeamMember.teamId = ownerTeam.id;
        ownerTeamMember = await TeamMemberService.create({
            data: ownerTeamMember,
            props: {
                isRoot: true,
                ignoreHooks: true,
            },
        });
        // Add permissions for this team.
        const ownerPermissions = new TeamPermission();
        ownerPermissions.permission = Permission.ProjectOwner;
        ownerPermissions.teamId = ownerTeam.id;
        ownerPermissions.projectId = createdItem.id;
        await TeamPermissionService.create({
            data: ownerPermissions,
            props: {
                isRoot: true,
                ignoreHooks: true,
            },
        });
        // Admin Team.
        const adminTeam = new Team();
        adminTeam.projectId = createdItem.id;
        adminTeam.name = "Admin";
        adminTeam.isPermissionsEditable = false;
        adminTeam.isTeamDeleteable = false;
        adminTeam.isTeamEditable = false;
        adminTeam.description =
            "This team is for project admins. Admins can invite members to any team and create project resources.";
        await TeamService.create({
            data: adminTeam,
            props: {
                isRoot: true,
            },
        });
        const adminPermissions = new TeamPermission();
        adminPermissions.permission = Permission.ProjectAdmin;
        adminPermissions.teamId = adminTeam.id;
        adminPermissions.projectId = createdItem.id;
        await TeamPermissionService.create({
            data: adminPermissions,
            props: {
                isRoot: true,
                ignoreHooks: true,
            },
        });
        // Members Team.
        const memberTeam = new Team();
        memberTeam.projectId = createdItem.id;
        memberTeam.isPermissionsEditable = true;
        memberTeam.name = "Members";
        memberTeam.isTeamDeleteable = true;
        memberTeam.description =
            "This team is for project members. Members can interact with any project resources like monitors, incidents, etc.";
        await TeamService.create({
            data: memberTeam,
            props: {
                isRoot: true,
            },
        });
        const memberPermissions = new TeamPermission();
        memberPermissions.permission = Permission.ProjectMember;
        memberPermissions.teamId = memberTeam.id;
        memberPermissions.projectId = createdItem.id;
        await TeamPermissionService.create({
            data: memberPermissions,
            props: {
                isRoot: true,
                ignoreHooks: true,
            },
        });
        await AccessTokenService.refreshUserAllPermissions(createdItem.createdByUserId);
        const user = await UserService.findOneById({
            id: createdItem.createdByUserId,
            props: {
                isRoot: true,
            },
            select: {
                isEmailVerified: true,
                email: true,
            },
        });
        if (user && user.isEmailVerified) {
            await UserNotificationRuleService.addDefaultNotificationRuleForUser(createdItem.id, user.id, user.email);
            await UserNotificationSettingService.addDefaultNotificationSettingsForUser(user.id, createdItem.id);
        }
        return createdItem;
    }
    async updateLastActive(projectId) {
        const key = projectId.toString();
        if (this.lastActiveCache.has(key)) {
            return;
        }
        /*
         * Set BEFORE the await so a burst of concurrent requests collapses to one
         * UPDATE per node per 60s window instead of all firing in parallel.
         */
        this.lastActiveCache.set(key, true, 60000);
        /*
         * Fire-and-forget — `lastActive` is a soft-real-time field and the
         * caller (auth middleware) shouldn't pay a Postgres round-trip for it.
         */
        void this.updateOneById({
            id: projectId,
            data: {
                lastActive: OneUptimeDate.getCurrentDate(),
            },
            props: {
                isRoot: true,
            },
        }).catch((err) => {
            // Drop the cache entry so a retry can fire within the same TTL window.
            this.lastActiveCache.delete(key);
            logger.error(`Failed to update Project.lastActive for ${key}: ${err.message}`);
        });
    }
    /**
     * Returns whether the given project requires SSO for login. Cached for
     * 60s in-process — middleware calls this on every authenticated request.
     */
    async getRequireSsoForLogin(projectId) {
        const key = projectId.toString();
        const cached = this.requireSsoForLoginCache.get(key);
        if (cached !== undefined) {
            return cached;
        }
        const project = await this.findOneById({
            id: projectId,
            select: { requireSsoForLogin: true },
            props: { isRoot: true },
        });
        if (!project) {
            // Don't cache "not found" — let the caller decide how to handle it.
            throw new BadDataException("Project not found");
        }
        const value = Boolean(project.requireSsoForLogin);
        this.requireSsoForLoginCache.set(key, value, 60000);
        return value;
    }
    async getOwners(projectId) {
        if (!projectId) {
            throw new BadDataException("Project ID is required");
        }
        // get teams with project owner permissions.
        const teamPermissions = await TeamPermissionService.findBy({
            query: {
                projectId: projectId,
                permission: Permission.ProjectOwner,
            },
            props: {
                isRoot: true,
            },
            limit: LIMIT_MAX,
            skip: 0,
            select: {
                teamId: true,
            },
        });
        if (teamPermissions.length === 0) {
            return [];
        }
        const teamIds = teamPermissions.map((item) => {
            return item.teamId;
        });
        return TeamMemberService.getUsersInTeams(teamIds);
    }
    async onBeforeFind(findBy) {
        var _a, _b;
        /*
         * if user has no project id, then he should not be able to access any project.
         * Master admins should be able to access all projects.
         */
        if (!findBy.props.isRoot &&
            !findBy.props.isMasterAdmin &&
            (!((_a = findBy.props.userGlobalAccessPermission) === null || _a === void 0 ? void 0 : _a.projectIds) ||
                ((_b = findBy.props.userGlobalAccessPermission) === null || _b === void 0 ? void 0 : _b.projectIds.length) === 0)) {
            findBy.props.isRoot = true;
            findBy.query._id = ObjectID.getZeroObjectID().toString(); // should not get any projects.
        }
        return { findBy, carryForward: null };
    }
    async onBeforeDelete(deleteBy) {
        const projects = await this.findBy({
            query: deleteBy.query,
            props: {
                isRoot: true,
            },
            limit: LIMIT_MAX,
            skip: 0,
            select: {
                _id: true,
                paymentProviderSubscriptionId: true,
                paymentProviderMeteredSubscriptionId: true,
                name: true,
                createdAt: true,
                planName: true,
                createdByUser: {
                    name: true,
                    email: true,
                },
            },
        });
        return { deleteBy, carryForward: projects };
    }
    async onDeleteSuccess(onDelete, _itemIdsBeforeDelete) {
        var _a, _b, _c, _d;
        if (NotificationSlackWebhookOnDeleteProject) {
            for (const project of onDelete.carryForward) {
                let subscriptionStatus = null;
                if (IsBillingEnabled) {
                    subscriptionStatus = await BillingService.getSubscriptionStatus(project.paymentProviderSubscriptionId);
                }
                let slackMessage = `*Project Deleted:*
*Project Name:* ${((_a = project.name) === null || _a === void 0 ? void 0 : _a.toString()) || "N/A"}
*Project ID:* ${((_b = project._id) === null || _b === void 0 ? void 0 : _b.toString()) || "N/A"}
*Project Created Date:* ${project.createdAt ? new Date(project.createdAt).toUTCString() : "N/A"}
*Project Plan Name:* ${((_c = project.planName) === null || _c === void 0 ? void 0 : _c.toString()) || "N/A"}
`;
                if (subscriptionStatus) {
                    slackMessage += `*Project Subscription Status:* ${(subscriptionStatus === null || subscriptionStatus === void 0 ? void 0 : subscriptionStatus.toString()) || "N/A"}
`;
                }
                if (project.createdByUser &&
                    project.createdByUser.name &&
                    project.createdByUser.email) {
                    slackMessage += `*Created By:* ${((_d = project === null || project === void 0 ? void 0 : project.createdByUser.name) === null || _d === void 0 ? void 0 : _d.toString()) + " (" + project.createdByUser.email.toString() + ")" || "N/A"}
`;
                }
                SlackUtil.sendMessageToChannelViaIncomingWebhook({
                    url: URL.fromString(NotificationSlackWebhookOnDeleteProject),
                    text: slackMessage,
                }).catch((err) => {
                    var _a;
                    // log this error but do not throw it. Not important enough to stop the process.
                    logger.error(err, {
                        projectId: (_a = project === null || project === void 0 ? void 0 : project.id) === null || _a === void 0 ? void 0 : _a.toString(),
                    });
                });
            }
        }
        // get project id
        if (IsBillingEnabled) {
            for (const project of onDelete.carryForward) {
                if (project.paymentProviderSubscriptionId) {
                    await BillingService.cancelSubscription(project.paymentProviderSubscriptionId);
                }
                if (project.paymentProviderMeteredSubscriptionId) {
                    await BillingService.cancelSubscription(project.paymentProviderMeteredSubscriptionId);
                }
            }
        }
        return onDelete;
    }
    async getCurrentPlan(projectId) {
        if (!IsBillingEnabled) {
            return { plan: null, isSubscriptionUnpaid: false };
        }
        const cacheKey = projectId.toString();
        const cached = this.currentPlanCache.get(cacheKey);
        if (cached !== undefined) {
            return cached;
        }
        const project = await this.findOneById({
            id: projectId,
            select: {
                paymentProviderPlanId: true,
                paymentProviderSubscriptionStatus: true,
                paymentProviderMeteredSubscriptionStatus: true,
            },
            props: {
                isRoot: true,
                ignoreHooks: true,
            },
        });
        if (!project) {
            // Don't cache "not found" — let the caller surface a fresh error.
            throw new BadDataException("Project ID is invalid");
        }
        if (!project.paymentProviderPlanId) {
            // Don't cache "no plan" — the project may be mid-onboarding.
            throw new BadDataException("Project does not have any plans");
        }
        const plan = SubscriptionPlan.getPlanType(project.paymentProviderPlanId, getAllEnvVars());
        const result = {
            plan: plan,
            isSubscriptionUnpaid: !BillingService.isSubscriptionActive(project.paymentProviderSubscriptionStatus) ||
                !BillingService.isSubscriptionActive(project.paymentProviderMeteredSubscriptionStatus),
        };
        this.currentPlanCache.set(cacheKey, result, 60000);
        return result;
    }
    async sendEmailToProjectOwners(projectId, subject, message) {
        const owners = await this.getOwners(projectId);
        if (owners.length === 0) {
            return;
        }
        for (const owner of owners) {
            MailService.sendMail({
                toEmail: owner.email,
                templateType: EmailTemplateType.SimpleMessage,
                vars: {
                    subject: subject,
                    message: message,
                },
                subject: subject,
            }, {
                projectId,
                userId: owner.id,
            }).catch((err) => {
                logger.error(err, {
                    projectId: projectId === null || projectId === void 0 ? void 0 : projectId.toString(),
                });
            });
        }
    }
    async reactiveSubscription(projectId) {
        logger.debug("Reactivating subscription for project " + projectId, {
            projectId: projectId === null || projectId === void 0 ? void 0 : projectId.toString(),
        });
        const project = await this.findOneById({
            id: projectId,
            props: {
                isRoot: true,
            },
            select: {
                _id: true,
                paymentProviderCustomerId: true,
                paymentProviderSubscriptionId: true,
                paymentProviderMeteredSubscriptionId: true,
                paymentProviderSubscriptionSeats: true,
                paymentProviderPlanId: true,
            },
        });
        if (!project) {
            throw new BadDataException("Project not found");
        }
        if (!project.paymentProviderCustomerId) {
            throw new BadDataException("Payment Provider customer not found");
        }
        if (!project.paymentProviderSubscriptionId) {
            throw new BadDataException("Payment Provider subscription not found");
        }
        if (!project.paymentProviderMeteredSubscriptionId) {
            throw new BadDataException("Payment Provider metered subscription not found");
        }
        if (!project.paymentProviderSubscriptionSeats) {
            throw new BadDataException("Payment Provider subscription seats not found");
        }
        if (!project.paymentProviderPlanId) {
            throw new BadDataException("Payment Provider plan id not found");
        }
        const subscriptionPlan = SubscriptionPlan.getSubscriptionPlanById(project.paymentProviderPlanId, getAllEnvVars());
        if (!subscriptionPlan) {
            throw new BadDataException("Subscription plan not found");
        }
        const result = await BillingService.changePlan({
            projectId: project.id,
            subscriptionId: project.paymentProviderSubscriptionId,
            meteredSubscriptionId: project.paymentProviderMeteredSubscriptionId,
            serverMeteredPlans: AllMeteredPlans,
            newPlan: subscriptionPlan,
            quantity: project.paymentProviderSubscriptionSeats,
            isYearly: SubscriptionPlan.isYearlyPlan(project.paymentProviderPlanId),
            endTrialAt: undefined,
        });
        // refresh subscription status.
        const subscriptionState = await BillingService.getSubscriptionStatus(result.subscriptionId);
        const meteredSubscriptionState = await BillingService.getSubscriptionStatus(project.paymentProviderMeteredSubscriptionId);
        // now update project with new subscription id.
        await this.updateOneById({
            id: project.id,
            data: {
                paymentProviderSubscriptionId: result.subscriptionId,
                paymentProviderMeteredSubscriptionId: result.meteredSubscriptionId,
                paymentProviderSubscriptionStatus: subscriptionState,
                paymentProviderMeteredSubscriptionStatus: meteredSubscriptionState,
            },
            props: {
                isRoot: true,
            },
        });
        // send slack message on plan change.
        await this.sendSubscriptionChangeWebhookSlackNotification(projectId);
    }
    getActiveProjectStatusQuery() {
        return {
            // get only active projects
            paymentProviderSubscriptionStatus: QueryHelper.equalToOrNull([
                SubscriptionStatus.Active,
                SubscriptionStatus.Trialing,
            ]),
            paymentProviderMeteredSubscriptionStatus: QueryHelper.equalToOrNull([
                SubscriptionStatus.Active,
                SubscriptionStatus.Trialing,
            ]),
        };
    }
    async getAllActiveProjects(params) {
        const select = (params === null || params === void 0 ? void 0 : params.select) || { _id: true };
        const props = (params === null || params === void 0 ? void 0 : params.props) || {
            isRoot: true,
        };
        return await this.findAllBy({
            query: this.getActiveProjectStatusQuery(),
            select,
            props,
            skip: params === null || params === void 0 ? void 0 : params.skip,
            limit: params === null || params === void 0 ? void 0 : params.limit,
        });
    }
    async getProjectLinkInDashboard(projectId) {
        const dashboardUrl = await DatabaseConfig.getDashboardUrl();
        return URL.fromString(dashboardUrl.toString()).addRoute(`/${projectId.toString()}`);
    }
    async incrementAndGetIncidentCounter(projectId) {
        const mutex = await Semaphore.lock({
            key: projectId.toString(),
            namespace: "ProjectService.incidentCounter",
        });
        try {
            await this.atomicIncrementColumnValueByOne({
                id: projectId,
                columnName: "incidentCounter",
            });
            const project = await this.findOneById({
                id: projectId,
                select: {
                    incidentCounter: true,
                    incidentNumberPrefix: true,
                },
                props: {
                    isRoot: true,
                },
            });
            if (!project || project.incidentCounter === undefined) {
                throw new BadDataException(`Could not read incidentCounter for project ${projectId.toString()}`);
            }
            return {
                counter: project.incidentCounter,
                prefix: project.incidentNumberPrefix,
            };
        }
        finally {
            await Semaphore.release(mutex);
        }
    }
    async incrementAndGetAlertCounter(projectId) {
        const mutex = await Semaphore.lock({
            key: projectId.toString(),
            namespace: "ProjectService.alertCounter",
        });
        try {
            await this.atomicIncrementColumnValueByOne({
                id: projectId,
                columnName: "alertCounter",
            });
            const project = await this.findOneById({
                id: projectId,
                select: {
                    alertCounter: true,
                    alertNumberPrefix: true,
                },
                props: {
                    isRoot: true,
                },
            });
            if (!project || project.alertCounter === undefined) {
                throw new BadDataException(`Could not read alertCounter for project ${projectId.toString()}`);
            }
            return {
                counter: project.alertCounter,
                prefix: project.alertNumberPrefix,
            };
        }
        finally {
            await Semaphore.release(mutex);
        }
    }
    async incrementAndGetScheduledMaintenanceCounter(projectId) {
        const mutex = await Semaphore.lock({
            key: projectId.toString(),
            namespace: "ProjectService.scheduledMaintenanceCounter",
        });
        try {
            await this.atomicIncrementColumnValueByOne({
                id: projectId,
                columnName: "scheduledMaintenanceCounter",
            });
            const project = await this.findOneById({
                id: projectId,
                select: {
                    scheduledMaintenanceCounter: true,
                    scheduledMaintenanceNumberPrefix: true,
                },
                props: {
                    isRoot: true,
                },
            });
            if (!project || project.scheduledMaintenanceCounter === undefined) {
                throw new BadDataException(`Could not read scheduledMaintenanceCounter for project ${projectId.toString()}`);
            }
            return {
                counter: project.scheduledMaintenanceCounter,
                prefix: project.scheduledMaintenanceNumberPrefix,
            };
        }
        finally {
            await Semaphore.release(mutex);
        }
    }
    async incrementAndGetIncidentEpisodeCounter(projectId) {
        const mutex = await Semaphore.lock({
            key: projectId.toString(),
            namespace: "ProjectService.incidentEpisodeCounter",
        });
        try {
            await this.atomicIncrementColumnValueByOne({
                id: projectId,
                columnName: "incidentEpisodeCounter",
            });
            const project = await this.findOneById({
                id: projectId,
                select: {
                    incidentEpisodeCounter: true,
                    incidentEpisodeNumberPrefix: true,
                },
                props: {
                    isRoot: true,
                },
            });
            if (!project || project.incidentEpisodeCounter === undefined) {
                throw new BadDataException(`Could not read incidentEpisodeCounter for project ${projectId.toString()}`);
            }
            return {
                counter: project.incidentEpisodeCounter,
                prefix: project.incidentEpisodeNumberPrefix,
            };
        }
        finally {
            await Semaphore.release(mutex);
        }
    }
    async incrementAndGetAlertEpisodeCounter(projectId) {
        const mutex = await Semaphore.lock({
            key: projectId.toString(),
            namespace: "ProjectService.alertEpisodeCounter",
        });
        try {
            await this.atomicIncrementColumnValueByOne({
                id: projectId,
                columnName: "alertEpisodeCounter",
            });
            const project = await this.findOneById({
                id: projectId,
                select: {
                    alertEpisodeCounter: true,
                    alertEpisodeNumberPrefix: true,
                },
                props: {
                    isRoot: true,
                },
            });
            if (!project || project.alertEpisodeCounter === undefined) {
                throw new BadDataException(`Could not read alertEpisodeCounter for project ${projectId.toString()}`);
            }
            return {
                counter: project.alertEpisodeCounter,
                prefix: project.alertEpisodeNumberPrefix,
            };
        }
        finally {
            await Semaphore.release(mutex);
        }
    }
    async isSMSNotificationsEnabled(projectId) {
        const project = await this.findOneById({
            id: projectId,
            select: {
                enableSmsNotifications: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!project) {
            throw new BadDataException("Project not found");
        }
        return Boolean(project.enableSmsNotifications);
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "onBeforeCreate", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "onBeforeUpdate", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "changePlan", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Model]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "onCreateSuccess", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Model]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "addDefaultAlertState", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Model]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "addDefaultAlertSeverity", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "updateLastActive", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "getRequireSsoForLogin", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "getOwners", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "onBeforeFind", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "onBeforeDelete", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Array]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "onDeleteSuccess", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "getCurrentPlan", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID, String, String]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "sendEmailToProjectOwners", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "reactiveSubscription", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "getAllActiveProjects", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "getProjectLinkInDashboard", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "incrementAndGetIncidentCounter", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "incrementAndGetAlertCounter", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "incrementAndGetScheduledMaintenanceCounter", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "incrementAndGetIncidentEpisodeCounter", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "incrementAndGetAlertEpisodeCounter", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], ProjectService.prototype, "isSMSNotificationsEnabled", null);
export default new ProjectService();
//# sourceMappingURL=ProjectService.js.map