Source: controllers/UserController.js

const BaseDatabaseController = require('./BaseDatabaseController');

/** Class representing a user controller. */
class UserController extends BaseDatabaseController {
    /** Create a user controller. */
    constructor() {
        super();
    }

    /**
     * Get a user by id.
     * 
     * @param {number} id
     * @returns {Promise<object>}
     */
    async byId(id) {
        const userResponse = await this._query('SELECT * FROM users WHERE id = ?', id);
        if (userResponse.length !== 1) {
            return null;
        }

        return userResponse[0];
    }

    /**
     * Get a user by account identifier.
     * 
     * @param {string} accountIdentifier
     * @returns {Promise<object>}
     */
    async byAccountIdentifier(accountIdentifier) {
        const userResponse = await this._query('SELECT * FROM users WHERE account_identifier = ?', accountIdentifier);
        if (userResponse.length !== 1) {
            return null;
        }

        return userResponse[0];
    }

    /**
     * Insert a user.
     * 
     * @param {string} name
     * @param {string} accountIdentifier
     * @param {string} authToken
     * @param {string} authMethod
     * @returns {Promise<object>}
     */
    async insert(name, accountIdentifier, authToken, authMethod) {
        const userResponse = await this._query('INSERT INTO users (name, account_identifier, auth_token, auth_method) VALUES (?, ?, ?, ?)', name, accountIdentifier, authToken, authMethod);
        return await this.byId(userResponse.insertId);
    }

    /**
     * Delete a user.
     * 
     * @param {number} userId
     * @returns {Promise<void>}
     */
    async delete(userId) {
        await this._query('DELETE FROM users WHERE id = ?', userId);
    }

    /**
     * Set a meta value for a user.
     * 
     * @param {number} userId
     * @param {string} name
     * @param {any} value
     * @returns {Promise<void>}
     */
    async setMeta(userId, name, value) {
        const existsCheck = await this._query('SELECT * FROM users_meta WHERE user_id = ? AND name = ?', userId, name);
        if (existsCheck.length === 0) {
            await this._query('INSERT INTO users_meta (user_id, name, value) VALUES (?, ?, ?)', userId, name, value);
        } else {
            await this._query('UPDATE users_meta SET value = ? WHERE user_id = ? AND name = ?', value, userId, name);
        }
    }

    /**
     * Get a meta value for a user.
     * 
     * @param {number} userId
     * @param {string} name
     * @returns {Promise<any>}
     */
    async getMeta(userId, name) {
        const meta = await this._query('SELECT * FROM users_meta WHERE user_id = ? AND name = ?', userId, name);
        if (meta.length === 0) {
            return null;
        }

        return meta[0].value;
    }

    /**
     * Add a contribution to a user.
     * 
     * @param {number} userId
     * @param {string} contributionCode
     * @returns {Promise<void>}
     */
    async addContribution(userId, contributionCode, contributionRecordId) {
        const contributionType = await this._query('SELECT * FROM contribution_types WHERE code = ?', contributionCode);
        if (contributionType.length !== 1) {
            return;
        }

        await this._query('INSERT INTO user_contributions (user_id, contribution_type_id, contribution_record_id) VALUES (?, ?, ?)', userId, contributionType[0].id, contributionRecordId);
    }

    /**
     * Get the contribution points of a user.
     * 
     * @param {number} userId
     * @returns {Promise<number>}
     */
    async getContributionPoints(userId) {
        const contributionPoints = await this._query('SELECT SUM(ct.point_value) AS points FROM user_contributions AS uc JOIN contribution_types AS ct WHERE uc.contribution_type_id = ct.id AND uc.user_id = ?', userId);
        return contributionPoints[0].points;
    }

    /**
     * Get the goals of a user.
     * 
     * @param {number} userId
     * @returns {Promise<object[]>}
     */
    async getGoals(userId) {
        const allGoals = await this._query('SELECT * FROM user_goals');
        const goals = [];
        
        for (const goal of allGoals) {
            const goalDescriptionsAndProgress = await this._query('SELECT ugd.id, ugd.target, ugd.description, ugp.progress FROM user_goal_descriptions AS ugd LEFT JOIN user_goal_progress AS ugp ON ugd.id = ugp.goal_desc_id WHERE ugd.goal_id = ? AND ugp.user_id = ?', goal.id, userId);
            if (goalDescriptionsAndProgress.length === 0) {
                continue;
            }
            
            const amountOfPeopleWhoAchievedGoal = await this._query('SELECT COUNT(DISTINCT user_id) AS count FROM user_goal_progress AS ugp JOIN user_goal_descriptions AS ugd ON ugp.goal_desc_id = ugd.id WHERE ugd.goal_id = ? AND ugp.progress >= ugd.target', goal.id);

            const goalData = {
                id: goal.id,
                name: goal.name,
                color: goal.color,
                icon: goal.icon,
                amountOfPeopleWhoAchievedGoal: amountOfPeopleWhoAchievedGoal[0].count,
                descriptions: goalDescriptionsAndProgress.map((goalDescription) => {
                    return {
                        id: goalDescription.id,
                        target: goalDescription.target,
                        description: goalDescription.description,
                        progress: goalDescription.progress,
                    };
                }),
            };

            goals.push(goalData);
        }

        return goals;
    }

    /**
     * Add goals to a user if they do not exist.
     * 
     * @param {number} userId
     * @returns {Promise<void>}
     */
    async addGoalsIfNotExists(userId) {
        const goals = await this._query('SELECT * FROM user_goals');
        const userGoals = await this._query('SELECT * FROM user_goal_progress WHERE user_id = ?', userId);
        for (const goal of goals) {
            if (userGoals.filter((userGoal) => userGoal.goal_desc_id === goal.id).length === 0) {
                const goalDescriptions = await this._query('SELECT * FROM user_goal_descriptions WHERE goal_id = ?', goal.id);
                for (const goalDescription of goalDescriptions) {
                    await this._query('INSERT INTO user_goal_progress (user_id, goal_desc_id) VALUES (?, ?)', userId, goalDescription.id);
                }
            }
        }
    }

    /**
     * Change the progress of a goal by the track name.
     * 
     * @param {number} userId
     * @param {string} goalName
     * @param {string} goalDesc
     * @param {number} changeByAmount
     * @returns {Promise<void>}
     */
    async changeGoalProgressByTrackName(userId, goalName, goalDesc, changeByAmount) {
        const goals = await this._query('SELECT * FROM user_goals WHERE name LIKE ?', `%${goalName}`);

        for (const goal of goals) {
            const goalDescriptions = await this._query('SELECT * FROM user_goal_descriptions WHERE goal_id = ? AND description = ?', goal.id, goalDesc);
            for (const goalDescription of goalDescriptions) {
                await this._query('UPDATE user_goal_progress SET progress = progress + ? WHERE user_id = ? AND goal_desc_id = ?', changeByAmount, userId, goalDescription.id);
            }
        }
    }

    /**
     * Get a contribution type by code.
     * 
     * @param {string} code
     * @returns {Promise<object>}
     */
    async getContributionType(code) {
        const contributionType = await this._query('SELECT * FROM contribution_types WHERE code = ?', code);
        if (contributionType.length !== 1) {
            return null;
        }

        return contributionType[0];
    }

    /**
     * Get the scans of a user.
     * 
     * @param {number} userId
     * @returns {Promise<object[]>}
     */
    async getScans(userId) {
        return await this._query('SELECT * FROM scans WHERE user_id = ?', userId);
    }
}

module.exports = UserController;