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 DatabaseConfig from "../DatabaseConfig";
import { IsBillingEnabled, NotificationSlackWebhookOnCreateUser, } from "../EnvironmentConfig";
import logger from "../Utils/Logger";
import DatabaseService from "./DatabaseService";
import EmailVerificationTokenService from "./EmailVerificationTokenService";
import MailService from "./MailService";
import TeamMemberService from "./TeamMemberService";
import UserNotificationRuleService from "./UserNotificationRuleService";
import UserNotificationSettingService from "./UserNotificationSettingService";
import UserSessionService from "./UserSessionService";
import { AccountsRoute } from "../../ServiceRoute";
import Route from "../../Types/API/Route";
import URL from "../../Types/API/URL";
import LIMIT_MAX from "../../Types/Database/LimitMax";
import OneUptimeDate from "../../Types/Date";
import Email from "../../Types/Email";
import EmailTemplateType from "../../Types/Email/EmailTemplateType";
import HashedString from "../../Types/HashedString";
import ObjectID from "../../Types/ObjectID";
import Text from "../../Types/Text";
import EmailVerificationToken from "../../Models/DatabaseModels/EmailVerificationToken";
import Model from "../../Models/DatabaseModels/User";
import SlackUtil from "../Utils/Workspace/Slack/Slack";
import UserTotpAuthService from "./UserTotpAuthService";
import UserWebAuthnService from "./UserWebAuthnService";
import BadDataException from "../../Types/Exception/BadDataException";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
export class Service 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);
    }
    /**
     * Debounced fire-and-forget update of `User.lastActive`. The auth
     * middleware calls this on every authenticated request; without the cache
     * we'd issue one Postgres UPDATE per request per user.
     */
    async updateLastActive(userId) {
        const key = userId.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.
         */
        this.lastActiveCache.set(key, true, 60000);
        void this.updateOneById({
            id: userId,
            data: { lastActive: OneUptimeDate.getCurrentDate() },
            props: { isRoot: true },
        }).catch((err) => {
            this.lastActiveCache.delete(key);
            logger.error(`Failed to update User.lastActive for ${key}: ${err.message}`);
        });
    }
    async getUserMarkdownString(data) {
        var _a, _b;
        const user = await this.findOneBy({
            query: {
                _id: data.userId,
            },
            select: {
                name: true,
                email: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!user) {
            return "";
        }
        return `[${((_a = user.name) === null || _a === void 0 ? void 0 : _a.toString()) || ((_b = user.email) === null || _b === void 0 ? void 0 : _b.toString()) || "User"}](${(await this.getUserLinkInDashboard(data.projectId, data.userId)).toString()})`;
    }
    async getUserLinkInDashboard(projectId, userId) {
        const dashboardUrl = await DatabaseConfig.getDashboardUrl();
        return URL.fromString(dashboardUrl.toString()).addRoute(`/${projectId.toString()}/settings/users/${userId.toString()}`);
    }
    async onCreateSuccess(_onCreate, createdItem) {
        var _a, _b, _c, _d;
        if (NotificationSlackWebhookOnCreateUser) {
            SlackUtil.sendMessageToChannelViaIncomingWebhook({
                url: URL.fromString(NotificationSlackWebhookOnCreateUser),
                text: `*New OneUptime User:* 
  *Email:* ${((_a = createdItem.email) === null || _a === void 0 ? void 0 : _a.toString()) || "N/A"}
  *Name:* ${((_b = createdItem.name) === null || _b === void 0 ? void 0 : _b.toString()) || "N/A"}
  *Phone:* ${((_c = createdItem.companyPhoneNumber) === null || _c === void 0 ? void 0 : _c.toString()) || "N/A"}
  *Company:* ${((_d = createdItem.companyName) === null || _d === void 0 ? void 0 : _d.toString()) || "N/A"}`,
            }).catch((err) => {
                var _a;
                // log this error but do not throw it. Not important enough to stop the process.
                logger.error(err, {
                    userId: (_a = createdItem.id) === null || _a === void 0 ? void 0 : _a.toString(),
                });
            });
        }
        // A place holder method used for overriding.
        return Promise.resolve(createdItem);
    }
    async findByEmail(email, props) {
        return await this.findOneBy({
            query: {
                email: email,
            },
            select: {
                _id: true,
            },
            props: props,
        });
    }
    async onBeforeUpdate(updateBy) {
        let carryForward = [];
        if (updateBy.data.password || updateBy.data.email) {
            const users = await this.findBy({
                query: updateBy.query,
                select: {
                    _id: true,
                    email: true,
                },
                props: {
                    isRoot: true,
                },
                limit: LIMIT_MAX,
                skip: 0,
            });
            carryForward = users;
        }
        if (updateBy.data.enableTwoFactorAuth) {
            // check if any two factor auth is verified.
            const users = await this.findBy({
                query: updateBy.query,
                select: {
                    _id: true,
                    email: true,
                },
                props: {
                    isRoot: true,
                },
                limit: LIMIT_MAX,
                skip: 0,
            });
            for (const user of users) {
                const totpAuth = await UserTotpAuthService.findOneBy({
                    query: {
                        userId: user.id,
                        isVerified: true,
                    },
                    select: {
                        _id: true,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                const webAuthn = await UserWebAuthnService.findOneBy({
                    query: {
                        userId: user.id,
                        isVerified: true,
                    },
                    select: {
                        _id: true,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                if (!totpAuth && !webAuthn) {
                    throw new BadDataException("Please verify two factor authentication method before you enable two factor authentication.");
                }
            }
        }
        return { updateBy, carryForward: carryForward };
    }
    async onBeforeDelete(deleteBy) {
        // Check if the user is a member of any project
        const users = await this.findBy({
            query: deleteBy.query,
            select: {
                _id: true,
            },
            props: {
                isRoot: true,
            },
            limit: LIMIT_MAX,
            skip: 0,
        });
        for (const user of users) {
            const teamMembers = await TeamMemberService.findBy({
                query: {
                    userId: user.id,
                },
                select: {
                    _id: true,
                    projectId: true,
                },
                limit: LIMIT_MAX,
                skip: 0,
                props: {
                    isRoot: true,
                },
            });
            if (teamMembers.length > 0) {
                throw new BadDataException("You cannot delete your account because you are a member of one or more projects. Please leave all projects before deleting your account.");
            }
        }
        return { deleteBy, carryForward: null };
    }
    async onUpdateSuccess(onUpdate, _updatedItemIds) {
        var _a;
        if (onUpdate && onUpdate.updateBy.data.password) {
            const host = await DatabaseConfig.getHost();
            const httpProtocol = await DatabaseConfig.getHttpProtocol();
            for (const user of onUpdate.carryForward) {
                // Revoke all active sessions for this user on password change
                await UserSessionService.revokeAllSessionsByUserId(user.id, {
                    reason: "Password changed",
                });
                // password changed, send password changed mail
                MailService.sendMail({
                    toEmail: user.email,
                    subject: "Password Changed.",
                    templateType: EmailTemplateType.PasswordChanged,
                    vars: {
                        homeURL: new URL(httpProtocol, host).toString(),
                    },
                }).catch((err) => {
                    var _a;
                    logger.error(err, { userId: (_a = user.id) === null || _a === void 0 ? void 0 : _a.toString() });
                });
            }
        }
        if (onUpdate && onUpdate.updateBy.data.isEmailVerified) {
            // if the email is verified then create default policies for this user.
            const newUsers = await this.findBy({
                query: onUpdate.updateBy.query,
                select: {
                    _id: true,
                    email: true,
                },
                props: {
                    isRoot: true,
                },
                limit: LIMIT_MAX,
                skip: 0,
            });
            for (const user of newUsers) {
                // emai is verified. create default policies for this user.
                const teamMembers = await TeamMemberService.findBy({
                    query: {
                        userId: user.id,
                        hasAcceptedInvitation: true,
                    },
                    select: {
                        projectId: true,
                    },
                    limit: LIMIT_MAX,
                    skip: 0,
                    props: {
                        isRoot: true,
                    },
                });
                for (const member of teamMembers) {
                    // create default policies for this user.
                    await UserNotificationRuleService.addDefaultNotificationRuleForUser(member.projectId, user.id, user.email);
                    await UserNotificationSettingService.addDefaultNotificationSettingsForUser(user.id, member.projectId);
                }
            }
        }
        if (onUpdate && onUpdate.updateBy.data.email) {
            const newUsers = await this.findBy({
                query: onUpdate.updateBy.query,
                select: {
                    _id: true,
                    email: true,
                    name: true,
                },
                props: {
                    isRoot: true,
                },
                limit: LIMIT_MAX,
                skip: 0,
            });
            for (const user of onUpdate.carryForward) {
                const newUser = newUsers.find((u) => {
                    var _a;
                    return ((_a = u._id) === null || _a === void 0 ? void 0 : _a.toString()) === user._id.toString();
                });
                if (newUser && ((_a = newUser.email) === null || _a === void 0 ? void 0 : _a.toString()) !== user.email.toString()) {
                    // password changed, send password changed mail
                    const generatedToken = ObjectID.generate();
                    const emailVerificationToken = new EmailVerificationToken();
                    emailVerificationToken.userId = user.id;
                    emailVerificationToken.email = newUser.email;
                    emailVerificationToken.token = generatedToken;
                    emailVerificationToken.expires = OneUptimeDate.getOneDayAfter();
                    await EmailVerificationTokenService.create({
                        data: emailVerificationToken,
                        props: {
                            isRoot: true,
                        },
                    });
                    const host = await DatabaseConfig.getHost();
                    const httpProtocol = await DatabaseConfig.getHttpProtocol();
                    MailService.sendMail({
                        toEmail: newUser.email,
                        subject: "You have changed your email. Please verify your email.",
                        templateType: EmailTemplateType.EmailChanged,
                        vars: {
                            name: newUser.name.toString(),
                            tokenVerifyUrl: new URL(httpProtocol, host, new Route(AccountsRoute.toString()).addRoute("/verify-email/" + generatedToken.toString())).toString(),
                            homeUrl: new URL(httpProtocol, host).toString(),
                        },
                    }).catch((err) => {
                        var _a;
                        logger.error(err, { userId: (_a = user.id) === null || _a === void 0 ? void 0 : _a.toString() });
                    });
                    await this.updateOneBy({
                        query: {
                            _id: user.id.toString(),
                        },
                        data: {
                            isEmailVerified: false,
                        },
                        props: {
                            isRoot: true,
                            ignoreHooks: true,
                        },
                    });
                }
            }
        }
        return onUpdate;
    }
    async createByEmail(data) {
        const { email, props } = data;
        const user = new Model();
        user.email = email;
        if (data.name) {
            user.name = data.name;
        }
        user.isEmailVerified = data.isEmailVerified || false;
        if (data.generateRandomPassword) {
            user.password = new HashedString(Text.generateRandomText(20));
        }
        if (!IsBillingEnabled) {
            // if billing is not enabled, then email is verified by default.
            user.isEmailVerified = true;
        }
        return await this.create({
            data: user,
            props: props,
        });
    }
    async getTimezoneForUser(userId) {
        const user = await this.findOneBy({
            query: {
                _id: userId,
            },
            select: {
                timezone: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!user) {
            return null;
        }
        return user.timezone || null;
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "updateLastActive", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getUserMarkdownString", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID,
        ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getUserLinkInDashboard", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Model]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onCreateSuccess", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Email, Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "findByEmail", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onBeforeUpdate", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onBeforeDelete", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Array]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onUpdateSuccess", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "createByEmail", null);
export default new Service();
//# sourceMappingURL=UserService.js.map