services/MemberService.jsjavascript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const errors_1 = require("../utils/errors.js");
const member_1 = __importDefault(require("../db/models/member"));
const seasonTeam_1 = __importDefault(require("../db/models/seasonTeam"));
const TeamService_1 = __importDefault(require("./TeamService"));
const accessControl_1 = require("../utils/accessControl");
const stripProtectedFields_1 = require("../utils/stripProtectedFields.js");
const { Op } = require("sequelize");
const { logger } = require("../plugins/winston");
/**
 * Service for managing member entities and their associations.
 * Handles CRUD operations and member-specific logic.
 *
 * @class MemberService
 */
class MemberService {
    /**
     * Get all members for a user.
     * @param {UUID[]} ownerIds - The owner IDs.
     * @param {UUID} actingUserId - The acting user ID.
     * @param {Object} options - Options for fetching members.
     * @param {boolean} options.all - Whether to fetch all members or only those associated with the user.
     * @returns {Promise<Array>} List of members.
     */
    async getAll(ownerIds, actingUserId, isAdmin = false, options = { all: false }) {
        logger.debug(`MemberService getAll ${JSON.stringify({ ownerIds, actingUserId, isAdmin, options })}`);
        const accessibleOwnerIds = ownerIds.length > 0
            ? await (0, accessControl_1.filterAccessibleOwnerIds)(ownerIds, actingUserId, isAdmin)
            : [actingUserId];
        return member_1.default.findAll({
            where: options.all ? {} : { UserId: { [Op.in]: accessibleOwnerIds } },
        });
    }
    /**
     * Find a member by ID.
     * @param {UUID} id - The member ID.
     * @param {UUID} actingUserId - The acting user ID.
     * @returns {Promise<Object|null>} The member object or null if not found.
     */
    async findById(id, actingUserId, isAdmin = false) {
        logger.debug(`MemberService findById ${JSON.stringify({ id, actingUserId, isAdmin })}`);
        const member = await member_1.default.findByPk(id);
        if (!member) {
            return null;
        }
        await (0, accessControl_1.checkReadAccess)(member.UserId, actingUserId, isAdmin);
        return member_1.default.findOne({ where: { id } }); // njsscan-ignore: node_nosqli_injection
    }
    /**
     * Get the total count of members.
     * @returns {Promise<number>} The count of members.
     */
    getCount() {
        logger.debug(`MemberService getCount`);
        return member_1.default.count();
    }
    /**
     * Get the trend of member creation and deletion.
     * @returns {Promise<number>} The difference between created and deleted members in the last 30 days.
     */
    getTrend() {
        logger.debug(`MemberService getTrend`);
        return Promise.all([
            member_1.default.count({
                where: {
                    createdAt: {
                        [Op.gt]: new Date().valueOf() - 1000 * 60 * 60 * 24 * 30,
                    },
                },
            }),
            member_1.default.count({
                where: {
                    deletedAt: {
                        [Op.gt]: new Date().valueOf() - 1000 * 60 * 60 * 24 * 30,
                    },
                },
            }),
        ]).then(([created, deleted]) => created - deleted);
    }
    /**
     * Create a new member.
     * @param {string} name - The name of the member.
     * @param {string} nickname - The nickname of the member.
     * @param {string | null} abbreviation - The abbreviation for the member.
     * @param {UUID} SeasonTeamId - The season team ID.
     * @param {UUID} actingUserId - The acting user ID.
     * @returns {Promise<Object>} The created member object.
     */
    async create(name, nickname, abbreviation, SeasonTeamId, actingUserId, isAdmin = false) {
        if (!abbreviation)
            abbreviation = name
                .split(" ")
                .map((s) => s.substring(0, 1))
                .join("");
        logger.debug(`MemberService create ${JSON.stringify({
            name,
            nickname,
            abbreviation,
            SeasonTeamId,
            actingUserId,
            isAdmin,
        })}`);
        // Inherit ownerId from parent SeasonTeam
        const foundSeasonTeam = await seasonTeam_1.default.findByPk(SeasonTeamId);
        if (!foundSeasonTeam) {
            throw new errors_1.NotFoundError(`SeasonTeam with ID ${SeasonTeamId} not found`);
        }
        const ownerId = foundSeasonTeam.UserId;
        await (0, accessControl_1.checkWriteAccess)(ownerId, actingUserId, isAdmin);
        const member = await member_1.default.create({
            name,
            nickname,
            abbreviation,
            SeasonTeamId,
            UserId: ownerId,
            creatorId: actingUserId,
            updaterId: actingUserId,
        });
        const seasonTeam = await seasonTeam_1.default.findByPk(SeasonTeamId);
        if (seasonTeam) {
            await TeamService_1.default.update(seasonTeam.TeamId, {}, actingUserId, isAdmin);
        }
        return member;
    }
    /**
     * Find or create a member.
     * @param {string} name - The name of the member.
     * @param {string} nickname - The nickname of the member.
     * @param {string | null} abbreviation - The abbreviation for the member.
     * @param {UUID} SeasonTeamId - The season team ID.
     * @param {UUID} actingUserId - The acting user ID.
     * @returns {Promise<Object>} The found or created member object.
     */
    async findOrCreate(name, nickname, abbreviation, SeasonTeamId, actingUserId, isAdmin = false) {
        logger.debug(`MemberService findOrCreate ${JSON.stringify({
            name,
            nickname,
            abbreviation,
            SeasonTeamId,
            actingUserId,
            isAdmin,
        })}`);
        // Inherit ownerId from parent SeasonTeam
        const foundSeasonTeam = await seasonTeam_1.default.findByPk(SeasonTeamId);
        if (!foundSeasonTeam) {
            throw new errors_1.NotFoundError(`SeasonTeam with ID ${SeasonTeamId} not found`);
        }
        const ownerId = foundSeasonTeam.UserId;
        await (0, accessControl_1.checkWriteAccess)(ownerId, actingUserId, isAdmin);
        const defaultAbbreviation = name
            .split(" ")
            .map((s) => s.substring(0, 1))
            .join("");
        const whereClause = {
            name,
            abbreviation: abbreviation || defaultAbbreviation,
            SeasonTeamId,
            UserId: ownerId,
        };
        if (nickname !== null) {
            whereClause.nickname = nickname;
        }
        const [member, _created] = await member_1.default.findOrCreate({
            where: { name, SeasonTeamId, UserId: ownerId },
            defaults: {
                name,
                abbreviation: abbreviation || defaultAbbreviation,
                SeasonTeamId,
                UserId: ownerId,
                creatorId: actingUserId,
                updaterId: actingUserId,
            },
        });
        return member;
    }
    /**
     * Update a member.
     * @param {UUID} id - The member ID.
     * @param {Object} data - The data to update.
     * @param {UUID} actingUserId - The acting user ID.
     * @param {boolean} [isAdmin=false]
     * @param {Object} [options={ all: false }] - Options for updating.
     * @param {boolean} [options.all=false] - Whether to update all members.
     * @returns {Promise<Object>} The updated member.
     */
    async update(id, data, actingUserId, isAdmin = false, options = { all: false }) {
        logger.debug(`MemberService update ${JSON.stringify({ id, data, actingUserId, isAdmin, options })}`);
        const foundMember = await member_1.default.findByPk(id);
        if (!foundMember) {
            logger.error(`No member found with ID ${id} when updating`);
            throw new errors_1.NotFoundError(`No member found with ID ${id} when updating`);
        }
        await (0, accessControl_1.checkWriteAccess)(foundMember.UserId, actingUserId, isAdmin);
        const oldSeasonTeamId = foundMember.SeasonTeamId;
        await foundMember.update({
            ...(0, stripProtectedFields_1.stripProtectedUpdateFields)(data),
            updaterId: actingUserId,
        });
        await foundMember.save();
        const seasonTeam = await seasonTeam_1.default.findByPk(foundMember.SeasonTeamId || oldSeasonTeamId);
        if (seasonTeam) {
            await TeamService_1.default.update(seasonTeam.TeamId, {}, actingUserId, isAdmin);
        }
        return member_1.default.findByPk(id);
    }
    /**
     * Remove a member.
     * @param {UUID} id - The member ID.
     * @param {UUID} actingUserId - The acting user ID.
     * @param {boolean} [isAdmin=false]
     * @param {Object} [options={ all: false }] - Options for removing members.
     * @param {boolean} [options.all=false] - Whether to remove all members.
     * @returns {Promise<void>} Resolves when the member is removed.
     */
    async remove(id, actingUserId, isAdmin = false, options = { all: false }) {
        logger.debug(`MemberService remove ${JSON.stringify({ id, actingUserId, isAdmin, options })}`);
        const foundMember = await member_1.default.findByPk(id);
        if (!foundMember) {
            logger.error(`No member found with ID ${id} when deleting`);
            throw new errors_1.NotFoundError(`No member found with ID ${id} when deleting`);
        }
        await (0, accessControl_1.checkDeleteAccess)(foundMember.UserId, actingUserId, isAdmin);
        const seasonTeamId = foundMember.SeasonTeamId;
        if (seasonTeamId) {
            const seasonTeam = await seasonTeam_1.default.findByPk(seasonTeamId);
            if (seasonTeam) {
                await TeamService_1.default.update(seasonTeam.TeamId, {}, actingUserId, isAdmin);
            }
        }
        return foundMember.destroy();
    }
    async migrateCreatorUpdater() {
        logger.debug(`MemberService migrateCreatorUpdater`);
        const members = await member_1.default.findAll({
            where: { creatorId: { [Op.is]: null }, UserId: { [Op.not]: null } },
        });
        await Promise.all(members.map((member) => member.update({ creatorId: member.UserId, updaterId: member.UserId })));
    }
}
exports.default = new MemberService();