'user strict'
const Discord = require('discord.js')
const { BaseClass } = require('./BaseClass')
const { ErrorCodes } = require('../Errors/ErrorCodes')
const { ValRnkAchv } = require('../Utils/ValRnkAchv')
const { RankManager } = require('../Managers/RankManager')
const { UserManager } = require('../Managers/UserManager')
const { RankBuilder } = require('./RankBuilder')
const { AchievementManager } = require('../Managers/AchievementManager')
const { AchievementBuilder } = require('./AchievementBuilder')
const { RankPriorityInUse, MissingValue, InvalidValue, InvalidDataArgument, DuplicateAchievement, RankNotFound, AchievementNotFound } = require('../Errors/LME')
/** @typedef {import('../../typings').GuildData} GuildData */
/** @typedef {import('../../typings').Rank} Rank */
/**
* Represents the data stored in the database, is different from the Discord.js Guild.
*/
class Guild extends BaseClass {
/** @type {Discord.Snowflake} - The ID of the guild. */
guildId
/** @type {string} - The name of the guild. */
guildName
/** @type {RankManager} - The manager for the ranks. */
ranks
/** @type {AchievementManager} - The manager for the achievements. */
achievements
/** @type {UserManager} - The manager for the users. */
users
/**
* @param {Discord.Client} client - The Discord Client.
* @param {GuildData} data - The data to create the guild with.
*/
constructor (client, data) {
super(client)
if (!data) throw new MissingValue(ErrorCodes.MissingArgument, 'data')
if (typeof data !== 'object') throw new InvalidDataArgument(ErrorCodes.InvalidDataArgument)
this.#checkData(data)
this.guildId = data.guildId
this.guildName = data.guildName
this.ranks = new RankManager(client, data.ranks)
this.achievements = new AchievementManager(client, data.achievements)
this.users = new UserManager(client, data.users)
}
/**
* Check if each property of the data is valid.
* @param {GuildData} data - The data to check.
*
* @throws {MissingValue} - If a property is missing.
* @throws {InvalidValue} - If a property is not valid.
*
* @ignore
*/
#checkData (data) {
if (!data.guildId) throw new MissingValue(ErrorCodes.MissingProperty, 'guildId')
if (typeof data.guildId !== 'string') throw new InvalidValue(ErrorCodes.InvalidValue, 'guildId', 'a string')
if (!data.guildName) throw new MissingValue(ErrorCodes.MissingProperty, 'guildName')
if (typeof data.guildName !== 'string') throw new InvalidValue(ErrorCodes.InvalidValue, 'guildName', 'a string')
if (!data.ranks) throw new MissingValue(ErrorCodes.MissingProperty, 'ranks')
if (!Array.isArray(data.ranks)) throw new InvalidValue(ErrorCodes.InvalidValue, 'ranks', 'an array')
if (data.ranks.some(rank => typeof rank !== 'object')) throw new InvalidValue(ErrorCodes.InvalidValue, 'ranks', 'an array of objects')
if (!data.achievements) throw new MissingValue(ErrorCodes.MissingProperty, 'achievements')
if (!Array.isArray(data.achievements)) throw new InvalidValue(ErrorCodes.InvalidValue, 'achievements', 'an array')
if (data.achievements.some(achievement => typeof achievement !== 'object')) throw new InvalidValue(ErrorCodes.InvalidValue, 'achievements', 'an array of objects')
if (!data.users) throw new MissingValue(ErrorCodes.MissingProperty, 'users')
if (!Array.isArray(data.users)) throw new InvalidValue(ErrorCodes.InvalidValue, 'users', 'an array')
if (data.users.some(user => typeof user !== 'object')) throw new InvalidValue(ErrorCodes.InvalidValue, 'users', 'an array of objects')
}
/**
* Add a rank to the guild data and update it the database.
* @param {RankBuilder} rank - The rank to add.
* @returns {Promise<Rank>} - The rank added.
*
* @throws {MissingValue} - If the rank is missing.
* @throws {InvalidValue} - If the rank is not a RankBuilder instance.
* @throws {RankPriorityInUse} - If the rank priority is already in use.
*/
async appendRank (rank) {
if (!rank) throw new MissingValue(ErrorCodes.MissingArgument, 'rank')
if (!(rank instanceof RankBuilder)) throw new InvalidValue(ErrorCodes.InvalidValue, 'rank', 'a RankBuilder instance')
await ValRnkAchv.validateRank(rank)
const rankData = rank.toJSON()
const existing = this.ranks.cache.get(rankData.priority.toString())
if (existing) throw new RankPriorityInUse(ErrorCodes.RankPriorityUsed, rankData.priority, existing.nameplate)
this.ranks.cache.set(rankData.priority.toString(), rankData)
// await GuildsData.findOneAndUpdate({ guildId: this.guildId }, { $push: { ranks: rankData } }).catch(console.log)
return rankData
}
/**
* Add an achievement to the guild data and update it the database.
* @param {AchievementBuilder} achievement - The achievement to add.
* @returns {Promise<Achievement>} - The achievement added.
*
* @throws {MissingValue} - If the achievement is missing.
* @throws {InvalidValue} - If the achievement is not an AchievementBuilder instance.
* @throws {DuplicateAchievement} - If the achievement name is already in use.
*/
async appendAchievement (achievement) {
if (!achievement) throw new MissingValue(ErrorCodes.MissingArgument, 'achievement')
if (!(achievement instanceof AchievementBuilder)) throw new InvalidValue(ErrorCodes.InvalidValue, 'achievement', 'an AchievementBuilder instance')
await ValRnkAchv.validateAchievement(achievement)
const achievementData = achievement.toJSON()
const existing = this.achievements.cache.get(achievementData.name)
if (existing) throw new DuplicateAchievement(ErrorCodes.DuplicateAchievement, achievementData.name)
this.achievements.cache.set(achievementData.name, achievementData)
// await GuildsData.findOneAndUpdate({ guildId: this.guildId }, { $push: { achievements: achievementData } }).catch(console.log)
for (const user of this.users.cache.values()) {
user.achievements.push(achievementData)
}
return achievementData
}
/**
* Remove a rank from the guild data and update it the database.
* @param {Number} priority - The priority of the rank to remove.
* @returns {Promise<Rank>} - The rank removed.
*/
async removeRank (priority) {
if (!priority) throw new MissingValue(ErrorCodes.MissingArgument, 'priority')
if (typeof priority !== 'number') throw new InvalidValue(ErrorCodes.InvalidValue, 'priority', 'a number')
const rank = this.ranks.cache.get(priority.toString())
if (!rank) throw new RankNotFound(ErrorCodes.RankNotFound, priority)
// Only delete rank in cache, const rank still exists
this.ranks.cache.delete(priority.toString())
// await GuildsData.findOneAndUpdate({ guildId: this.guildId }, { $pull: { ranks: { priority } } }).catch(console.log)
return rank
}
/**
* Remove an achievement from the guild data and update it the database.
*
* **NOTE: When an achievement is removed, it is removed from all users.**
* @param {String} name - The name of the achievement to remove.
* @returns {Promise<Achievement>} - The achievement removed.
*/
async removeAchievement (name) {
if (!name) throw new MissingValue(ErrorCodes.MissingArgument, 'name')
if (typeof name !== 'string') throw new InvalidValue(ErrorCodes.InvalidValue, 'name', 'a string')
const achievement = this.achievements.cache.get(name)
if (!achievement) throw new AchievementNotFound(ErrorCodes.AchievementNotFound, name)
this.achievements.cache.delete(name)
// await GuildsData.findOneAndUpdate({ guildId: this.guildId }, { $pull: { achievements: { name } } }).catch(console.log)
for (const user of this.users.cache.values()) {
user.achievements = user.achievements.filter(achievement => achievement.name !== name)
}
return achievement
}
/**
* Convert the guild data to a JSON object used for saving to the database.
* @returns {GuildData}
*/
toJSON () {
return {
guildId: this.guildId,
guildName: this.guildName,
ranks: this.ranks.cache.map(rank => rank),
achievements: this.achievements.cache.map(achievement => achievement),
users: this.users.cache.map(user => user.toJSON())
}
}
}
module.exports = { Guild }