services/ClubService.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 club_1 = __importDefault(require("../db/models/club"));
const seasonTeam_1 = __importDefault(require("../db/models/seasonTeam"));
const team_1 = __importDefault(require("../db/models/team"));
const ChoreoService_1 = __importDefault(require("./ChoreoService"));
const HitService_1 = __importDefault(require("./HitService"));
const MemberService_1 = __importDefault(require("./MemberService"));
const SeasonService_1 = __importDefault(require("./SeasonService"));
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");
const defaultInclude = [
    { association: "User" },
    {
        model: team_1.default,
        as: "Teams",
        include: [
            {
                model: seasonTeam_1.default,
                as: "SeasonTeams",
                include: [
                    { association: "Choreos", include: [{ association: "User" }] },
                    { association: "Season" },
                    { association: "Members" },
                ],
            },
        ],
    },
];
/**
 * Service for managing club entities and their associations.
 * Handles CRUD operations and club-specific logic.
 *
 * @class ClubService
 */
class ClubService {
    /**
     * Get all Clubs with specified ownerId
     *
     * @param {string[] | null} ownerIds
     * @param {UUID} actingUserId
     * @param {boolean} [isAdmin=false]
     * @param {Object} [options={ all: false }]
     * @param {boolean} [options.all=false]
     * @returns {Club[]}
     */
    async getAll(ownerIds, actingUserId, isAdmin = false, options = { all: false }) {
        logger.debug(`ClubService getAll ${JSON.stringify({ ownerIds, actingUserId, isAdmin, options })}`);
        const accessibleOwnerIds = ownerIds && ownerIds.length > 0
            ? await (0, accessControl_1.filterAccessibleOwnerIds)(ownerIds, actingUserId, isAdmin)
            : [actingUserId];
        return club_1.default.findAll({
            where: options.all
                ? {}
                : accessibleOwnerIds.length > 0
                    ? { UserId: { [Op.in]: accessibleOwnerIds } }
                    : { UserId: actingUserId },
            include: defaultInclude,
            order: [
                ["createdAt", "ASC"],
                [club_1.default.associations.Teams, "name"],
                [
                    club_1.default.associations.Teams,
                    team_1.default.associations.SeasonTeams,
                    seasonTeam_1.default.associations.Season,
                    "year",
                    "DESC NULLS LAST",
                ],
                [
                    club_1.default.associations.Teams,
                    team_1.default.associations.SeasonTeams,
                    seasonTeam_1.default.associations.Season,
                    "name",
                ],
            ],
        });
    }
    /**
     * Get the number of Clubs in the DB
     *
     * @returns {number}
     */
    getCount() {
        logger.debug(`ClubService getCount`);
        return club_1.default.count();
    }
    /**
     * Get the number of new Clubs minus then number of deleted Clubs in the last 30 days
     *
     * @returns {number}
     */
    getTrend() {
        logger.debug(`ClubService getTrend`);
        return Promise.all([
            club_1.default.count({
                where: {
                    createdAt: {
                        [Op.gt]: new Date().valueOf() - 1000 * 60 * 60 * 24 * 30,
                    },
                },
            }),
            club_1.default.count({
                where: {
                    deletedAt: {
                        [Op.gt]: new Date().valueOf() - 1000 * 60 * 60 * 24 * 30,
                    },
                },
            }),
        ]).then(([created, deleted]) => created - deleted);
    }
    /**
     * Find Club by its ID
     *
     * @param {UUID} id
     * @param {UUID} actingUserId
     * @param {boolean} [isAdmin=false]
     * @returns {Club}
     */
    async findById(id, actingUserId, isAdmin = false) {
        logger.debug(`ClubService findById ${JSON.stringify({ id, actingUserId, isAdmin })}`);
        const club = await club_1.default.findByPk(id);
        if (!club) {
            return null;
        }
        await (0, accessControl_1.checkReadAccess)(club.UserId, actingUserId, isAdmin);
        return club_1.default.findOne({
            where: { id },
            include: defaultInclude,
            order: [
                [club_1.default.associations.Teams, "name"],
                [
                    club_1.default.associations.Teams,
                    team_1.default.associations.SeasonTeams,
                    seasonTeam_1.default.associations.Season,
                    "year",
                    "DESC NULLS LAST",
                ],
                [
                    club_1.default.associations.Teams,
                    team_1.default.associations.SeasonTeams,
                    seasonTeam_1.default.associations.Members,
                    "name",
                ],
            ],
        }); // njsscan-ignore: node_nosqli_injection
    }
    /**
     * Find Club by its name
     *
     * @param {string} name
     * @param {UUID[]} ownerIds - Array of owner IDs the acting user has access to
     * @param {UUID} actingUserId
     * @param {boolean} [isAdmin=false]
     * @returns {Club[]}
     */
    async findByName(name, ownerIds, actingUserId, isAdmin = false) {
        logger.debug(`ClubService findByName ${JSON.stringify({ name, ownerIds, actingUserId, isAdmin })}`);
        const accessibleOwnerIds = ownerIds.length > 0
            ? await (0, accessControl_1.filterAccessibleOwnerIds)(ownerIds, actingUserId, isAdmin)
            : [actingUserId];
        return club_1.default.findAll({
            where: accessibleOwnerIds.length > 0
                ? { name, UserId: { [Op.in]: accessibleOwnerIds } }
                : { name },
            include: defaultInclude,
        });
    }
    /**
     * Create a new Club
     *
     * @param {string} name
     * @param {UUID|null} ownerId - Owner ID. If null/undefined, falls back to actingUserId
     * @param {UUID} actingUserId
     * @param {boolean} [isAdmin=false]
     * @returns {Club}
     */
    async create(name, ownerId, actingUserId, isAdmin = false) {
        logger.debug(`ClubService create ${JSON.stringify({ name, ownerId, actingUserId, isAdmin })}`);
        await (0, accessControl_1.checkWriteAccess)(ownerId, actingUserId, isAdmin);
        return club_1.default.create({
            name,
            UserId: ownerId,
            creatorId: actingUserId,
            updaterId: actingUserId,
        }).then((club) => club_1.default.count({ where: { UserId: ownerId } }).then(async (count) => {
            if (count <= 1) {
                await this.seedDemo(club, ownerId, actingUserId);
                const foundClub = await this.findById(club.id, actingUserId, isAdmin);
                if (!foundClub) {
                    logger.error(`Club with id ${club.id} not found after creation`);
                    throw new errors_1.NotFoundError(`Club with id ${club.id} not found after creation`);
                }
                club = foundClub;
            }
            return club;
        }));
    }
    /**
     * Seed a demo team and choreo for a Club
     *
     * @param {Club} club
     * @param {UUID} ownerId
     * @param {UUID} actingUserId
     * @returns {void}
     */
    async seedDemo(club, ownerId, actingUserId) {
        logger.debug(`ClubService seedDemo ${JSON.stringify({ club, ownerId, actingUserId })}`);
        return SeasonService_1.default.getAll(null, actingUserId).then((seasons) => {
            const currentSeason = seasons.find((s) => s.year == new Date().getFullYear());
            return TeamService_1.default.create("Demo-Team", club.id, currentSeason.id, ownerId, actingUserId).then((team) => {
                if (!team)
                    return;
                const seasonTeam = team.SeasonTeams[0];
                return Promise.all([
                    MemberService_1.default.create("Tina Turnerin", "Tini", "T", seasonTeam.id, actingUserId),
                    MemberService_1.default.create("Zoe Zuverlässig", "Zoe", "Z", seasonTeam.id, actingUserId),
                    MemberService_1.default.create("Fenja Flyer", "Fipsi", "F", seasonTeam.id, actingUserId),
                ]).then((members) => ChoreoService_1.default.create("Demo-Choreo", 25, undefined, seasonTeam.id, members.map((m) => ({ id: m.id })), ownerId, actingUserId).then((choreo) => {
                    if (!choreo)
                        return;
                    return Promise.all([
                        HitService_1.default.create("Pose", 0, choreo.id, members.map((m) => m.id), actingUserId),
                        HitService_1.default.create("Flick-Flack", 2, choreo.id, [members[0].id], actingUserId),
                    ]);
                }));
            });
        });
    }
    /**
     * Find or create a club.
     *
     * @param {string} name
     * @param {UUID|null} ownerId - Owner ID. If null/undefined, falls back to actingUserId
     * @param {UUID} actingUserId
     * @param {boolean} [isAdmin=false]
     * @returns {Club}
     */
    async findOrCreate(name, ownerId, actingUserId, isAdmin = false) {
        logger.debug(`ClubService findOrCreate ${JSON.stringify({ name, ownerId, actingUserId, isAdmin })}`);
        await (0, accessControl_1.checkWriteAccess)(ownerId, actingUserId, isAdmin);
        const [club, created] = await club_1.default.findOrCreate({
            where: { name, UserId: ownerId },
            defaults: {
                name,
                UserId: ownerId,
                creatorId: actingUserId,
                updaterId: actingUserId,
            },
        });
        return [club, created];
    }
    /**
     * Update a Club
     *
     * @param {UUID} id
     * @param {Object} data
     * @param {UUID} actingUserId
     * @param {boolean} [isAdmin=false]
     * @param {Object} [options={ all: false }]
     * @param {boolean} [options.all=false]
     * @returns {Club}
     */
    async update(id, data, actingUserId, isAdmin = false, options = { all: false }) {
        logger.debug(`ClubService update ${JSON.stringify({ id, data, actingUserId, isAdmin, options })}`);
        const club = await club_1.default.findByPk(id);
        if (!club) {
            throw new errors_1.NotFoundError(`No club found with ID ${id} when updating`);
        }
        await (0, accessControl_1.checkWriteAccess)(club.UserId, actingUserId, isAdmin);
        await club.update({
            ...(0, stripProtectedFields_1.stripProtectedUpdateFields)(data),
            updaterId: actingUserId,
        });
        return club.save();
    }
    /**
     * Remove a Club
     *
     * @param {UUID} id
     * @param {UUID} actingUserId
     * @param {boolean} [isAdmin=false]
     * @param {Object} [options={ all: false }]
     * @param {boolean} [options.all=false]
     * @returns {Promise<void>}
     */
    async remove(id, actingUserId, isAdmin = false, options = { all: false }) {
        logger.debug(`ClubService remove ${JSON.stringify({ id, actingUserId, isAdmin, options })}`);
        const club = await club_1.default.findByPk(id);
        if (!club) {
            throw new errors_1.NotFoundError(`No club found with ID ${id} when deleting`);
        }
        await (0, accessControl_1.checkDeleteAccess)(club.UserId, actingUserId, isAdmin);
        return club.destroy();
    }
    async migrateCreatorUpdater() {
        logger.debug(`ClubService migrateCreatorUpdater`);
        const clubs = await club_1.default.findAll({
            where: { creatorId: { [Op.is]: null }, UserId: { [Op.not]: null } },
        });
        await Promise.all(clubs.map((club) => club.update({ creatorId: club.UserId, updaterId: club.UserId })));
    }
}
exports.default = new ClubService();