const crypto = require("node:crypto") const logger = require("../modules/logger") const database = require("../modules/database") const { DefaultError } = require("../errors/errors") async function addPropertyToPlayer(key, value, uuid) { try { const sql = `INSERT INTO playersProperties (name, value, uuid) VALUES (?, ?, ?)` const result = await database.query(sql, [key, value, uuid]) if (result.affectedRows > 0) { return { code: 200, key, value, uuid } } else { throw new DefaultError(500, "Please contact an administrator.", "InternalServerError") } } catch (error) { logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Please contact an administrator.", "InternalServerError") } } async function deletePropertyToPlayer(key, uuid) { try { const sql = `DELETE FROM playersProperties WHERE name = ? AND uuid = ?` const result = await database.query(sql, [key, uuid]) if (result.affectedRows > 0) { return { code: 200, key, uuid } } else { throw new DefaultError(500, "Property not found for this user/key combination.") } } catch (error) { if (error instanceof DefaultError) throw error logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Please contact an administrator.", "InternalServerError") } } async function updatePropertyToPlayer(key, value, uuid) { try { const sql = `UPDATE playersProperties SET value = ? WHERE name = ? AND uuid = ?` const result = await database.query(sql, [value, key, uuid]) if (result.affectedRows > 0) { return { code: 200, key, value, uuid } } else { throw new DefaultError(404, "Property not found for this user/key combination") } } catch (error) { if (error instanceof DefaultError) throw error logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Please contact an administrator.", "InternalServerError") } } async function getPlayerProperties(uuid) { try { const sql = `SELECT * FROM playersProperties WHERE uuid = ?` const rows = await database.query(sql, [uuid]) if (rows.length === 0) { throw new DefaultError(404, "Properties not found for this user") } return { code: 200, properties: rows.map(property => ({ name: property.name, value: property.value })) } } catch (error) { if (error instanceof DefaultError) throw error logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Please contact an administrator.", "InternalServerError") } } async function getPlayerProperty(key, uuid) { try { const sql = `SELECT * FROM playersProperties WHERE name = ? AND uuid = ?` const rows = await database.query(sql, [key, uuid]) const property = rows[0] if (!property) { throw new DefaultError(404, "Property not found for this user/key combination") } return { code: 200, property } } catch (error) { if (error instanceof DefaultError) throw error logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Please contact an administrator.", "InternalServerError") } } async function getPlayerSettingsSchema() { const RAW_SCHEMA_CACHE = { privileges: {}, preferences: {} } try { const privilegesRows = await database.query("DESCRIBE playersPrivileges") const preferencesRows = await database.query("DESCRIBE playersPreferences") RAW_SCHEMA_CACHE.privileges = privilegesRows.map(c => c.Field).filter(n => n !== "uuid") RAW_SCHEMA_CACHE.preferences = preferencesRows.map(c => c.Field).filter(n => n !== "uuid") return RAW_SCHEMA_CACHE } catch (err) { logger.log("Database Schema Error: " + err.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Schema Error") } } async function updatePlayerPreferences(uuid, updates) { try { const keys = Object.keys(updates) if (keys.length === 0) { throw new DefaultError(400, "No fields provided for update.") } const setClause = keys.map(key => `\`${key}\` = ?`).join(', ') const sql = `UPDATE playersPreferences SET ${setClause} WHERE uuid = ?` const values = keys.map(key => updates[key]) values.push(uuid) const result = await database.query(sql, values) if (result.affectedRows > 0) { return { code: 200, message: "Preferences updated successfully." } } else { throw new DefaultError(404, "Player preferences not found or no changes made.") } } catch (error) { if (error instanceof DefaultError) throw error logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Please contact an administrator.", "InternalServerError") } } async function getPlayerPreferences(uuid) { try { const sql = `SELECT profanityFilter FROM playersPreferences WHERE uuid = ?` const rows = await database.query(sql, [uuid]) const data = rows[0] if (data) { return { code: 200, message: "Preferences retrieved successfully.", data: data } } else { throw new DefaultError(404, "Preferences not found for this UUID.") } } catch (error) { if (error instanceof DefaultError) throw error logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Please contact an administrator.", "InternalServerError") } } async function getPlayerPrivileges(uuid) { try { const sql = ` SELECT onlineChat, multiplayerServer, multiplayerRealms, telemetry FROM playersPrivileges WHERE uuid = ? ` const rows = await database.query(sql, [uuid]) const data = rows[0] if (data) { return { code: 200, message: "Privileges retrieved successfully.", data: data } } else { throw new DefaultError(404, "Privileges not found for this UUID.") } } catch (error) { if (error instanceof DefaultError) throw error logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Please contact an administrator.", "InternalServerError") } } async function updatePlayerPrivileges(uuid, updates) { try { const keys = Object.keys(updates) if (keys.length === 0) { throw new DefaultError(404, "No fields provided for update.") } const setClause = keys.map(key => `\`${key}\` = ?`).join(', ') const sql = `UPDATE playersPrivileges SET ${setClause} WHERE uuid = ?` const values = keys.map(key => updates[key]) values.push(uuid) const result = await database.query(sql, values) if (result.affectedRows > 0) { return { code: 200, message: "Privileges updated successfully." } } else { throw new DefaultError(404, "Player privileges not found or no changes made.") } } catch (error) { if (error instanceof DefaultError) throw error logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Please contact an administrator.", "InternalServerError") } } async function banUser(uuid, { reasonKey, reasonMessage, expires = null }) { try { if (!uuid || !reasonKey) { throw new DefaultError(400, "Missing uuid or reasonKey.") } let reasonId const reasonRows = await database.query("SELECT id FROM banReasons WHERE reason_key = ?", [reasonKey]) if (reasonRows.length > 0) { reasonId = reasonRows[0].id } else { const insertReason = await database.query("INSERT INTO banReasons (reason_key) VALUES (?)", [reasonKey]) reasonId = insertReason.insertId } const banId = crypto.randomUUID() const insertSql = ` INSERT INTO bans (banId, uuid, reason, reasonMessage, expires) VALUES (?, ?, ?, ?, ?) ` const result = await database.query(insertSql, [banId, uuid, reasonId, reasonMessage || "Banned by operator", expires]) if (result.affectedRows > 0) { return { code: 200, message: "User successfully banned.", banId: banId } } else { throw new DefaultError(500, "Failed to ban user.") } } catch (error) { if (error instanceof DefaultError) throw error if (error.code === "ER_NO_REFERENCED_ROW_2" || error.toString().includes("foreign key constraint")) { throw new DefaultError(404, "User not found (cannot ban a ghost).") } logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Internal Server Error", error.toString()) } } async function unbanUser(uuid) { try { if (!uuid) { throw new DefaultError(400, "Missing uuid.") } const sql = "DELETE FROM bans WHERE uuid = ?" const result = await database.query(sql, [uuid]) if (result.affectedRows > 0) { return { code: 200, message: "User successfully unbanned.", count: result.affectedRows } } else { throw new DefaultError(404, "User was not banned.") } } catch (error) { if (error instanceof DefaultError) throw error logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Please contact an administrator.", "InternalServerError") } } async function getPlayerBans(uuid) { try { const sql = ` SELECT b.banId, b.expires, b.reasonMessage, r.reason_key as reason FROM bans b JOIN banReasons r ON b.reason = r.id WHERE b.uuid = ? ORDER BY b.expires ASC ` const rows = await database.query(sql, [uuid]) if (rows.length > 0) { return { code: 200, bans: rows } } else { return { code: 204 } } } catch (error) { if (error instanceof DefaultError) throw error logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Please contact an administrator.", "InternalServerError") } } async function changeUsername(uuid, newName) { try { const sql = "UPDATE players SET username = ? WHERE uuid = ?" const result = await database.query(sql, [newName, uuid]) if (result.affectedRows > 0) { return { code: 200, message: "Username changed successfully" } } else { throw new DefaultError(404, "User not found") } } catch (error) { if (error instanceof DefaultError) throw error if (error.code === "ER_DUP_ENTRY" || error.errno === 1062) { throw new DefaultError(409, "Username already taken", "ForbiddenOperationException") } logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Internal Server Error", error.toString()) } } async function createTexture(uuid, hash, type, url, alias) { try { const sql = ` INSERT INTO textures (uuid, hash, type, url, alias) VALUES (?, ?, ?, ?, ?) ` await database.query(sql, [uuid, hash, type, url, alias]) return true } catch (error) { if (error.code === 'ER_DUP_ENTRY') { return false } logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function getTextureByUuid(textureUuid) { try { const sql = "SELECT hash FROM textures WHERE uuid = ?" const rows = await database.query(sql, [textureUuid]) return rows[0] || null } catch (error) { logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function getTextureByHash(hash) { try { const sql = "SELECT uuid FROM textures WHERE hash = ?" const rows = await database.query(sql, [hash]) return rows[0] } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function resetSkin(uuid, hash, variant) { try { const insertSql = ` INSERT IGNORE INTO playersSkins (playerUuid, assetHash, variant, isSelected) VALUES (?, ?, ?, 0) ` await database.query(insertSql, [uuid, hash, variant]) const updateSql = ` UPDATE playersSkins SET isSelected = (assetHash = ?) WHERE playerUuid = ? ` await database.query(updateSql, [hash, uuid]) return { code: 200 } } catch (error) { logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function hideCape(uuid) { try { const sql = "UPDATE playersCapes SET isSelected = 0 WHERE playerUuid = ?" await database.query(sql, [uuid]) return { code: 200 } } catch (error) { logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function showCape(uuid, hash) { try { const sql = ` UPDATE playersCapes SET isSelected = (assetHash = ?) WHERE playerUuid = ? ` const result = await database.query(sql, [hash, uuid]) return { code: 200, changed: result.affectedRows > 0 } } catch (error) { logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function checkCapeOwnership(uuid, hash) { try { const sql = "SELECT 1 FROM playersCapes WHERE playerUuid = ? AND assetHash = ?" const rows = await database.query(sql, [uuid, hash]) return rows.length > 0 } catch (error) { throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function getPlayerMeta(uuid) { try { const sql = `SELECT createdAt, nameChangeAllowed FROM players WHERE uuid = ?` const rows = await database.query(sql, [uuid]) return rows[0] } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function getLastNameChange(uuid) { try { const sql = ` SELECT changedAt FROM uuidToNameHistory WHERE uuid = ? AND changedAt IS NOT NULL ORDER BY changedAt DESC LIMIT 1 ` const rows = await database.query(sql, [uuid]) return rows[0] } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function getPlayerCertificate(uuid) { try { const sql = "SELECT * FROM playerCertificates WHERE uuid = ?" const rows = await database.query(sql, [uuid]) return rows[0] } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function savePlayerCertificate(uuid, privateKey, publicKey, signatureV2, expiresAt, refreshedAfter) { try { const sql = ` REPLACE INTO playerCertificates (uuid, privateKey, publicKey, publicKeySignatureV2, expiresAt, refreshedAfter) VALUES (?, ?, ?, ?, ?, ?) ` const result = await database.query(sql, [uuid, privateKey, publicKey, signatureV2, expiresAt, refreshedAfter]) return result.affectedRows > 0 } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function deleteExpiredCertificates(isoDate) { try { const sql = "DELETE FROM playerCertificates WHERE expiresAt < ?" const result = await database.query(sql, [isoDate]) return result.affectedRows } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function addProfileAction(uuid, actionCode) { try { const cleanUuid = uuid.replace(/-/g, "") const sql = "INSERT IGNORE INTO playerProfileActions (uuid, action) VALUES (?, ?)" const result = await database.query(sql, [cleanUuid, actionCode]) return result.affectedRows > 0 } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function removeProfileAction(uuid, actionCode) { try { const cleanUuid = uuid.replace(/-/g, "") const sql = "DELETE FROM playerProfileActions WHERE uuid = ? AND action = ?" const result = await database.query(sql, [cleanUuid, actionCode]) return result.affectedRows } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function getPlayerActions(uuid) { try { const cleanUuid = uuid.replace(/-/g, "") const sql = "SELECT action FROM playerProfileActions WHERE uuid = ?" const rows = await database.query(sql, [cleanUuid]) return rows.map(r => r.action) } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function clearAllPlayerActions(uuid) { try { const cleanUuid = uuid.replace(/-/g, "") const sql = "DELETE FROM playerProfileActions WHERE uuid = ?" const result = await database.query(sql, [cleanUuid]) return result.affectedRows } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function blockPlayer(blockerUuid, blockedUuid) { try { const sql = `INSERT IGNORE INTO playersBlockslist (blockerUuid, blockedUuid) VALUES (?, ?)` const result = await database.query(sql, [blockerUuid, blockedUuid]) return result.affectedRows > 0 } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function unblockPlayer(blockerUuid, blockedUuid) { try { const sql = `DELETE FROM playersBlockslist WHERE blockerUuid = ? AND blockedUuid = ?` const result = await database.query(sql, [blockerUuid, blockedUuid]) return result.affectedRows > 0 } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function getBlockedUuids(blockerUuid) { try { const sql = `SELECT blockedUuid FROM playersBlockslist WHERE blockerUuid = ?` const rows = await database.query(sql, [blockerUuid]) return rows.map(r => r.blockedUuid) } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function isBlocked(blockerUuid, targetUuid) { try { const sql = `SELECT 1 FROM playersBlockslist WHERE blockerUuid = ? AND blockedUuid = ? LIMIT 1` const rows = await database.query(sql, [blockerUuid, targetUuid]) return rows.length > 0 } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function getUsersByNames(usernames) { try { if (!usernames || usernames.length === 0) return [] const uniqueNames = [...new Set(usernames)] const placeholders = uniqueNames.map(() => "?").join(", ") const sql = `SELECT uuid, username FROM players WHERE username IN (${placeholders})` const rows = await database.query(sql, uniqueNames) return rows } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function getUuidAndUsername(username) { try { const sql = "SELECT uuid, username FROM players WHERE username = ?" const rows = await database.query(sql, [username]) return rows[0] || null } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function getProfileByUsername(username) { try { const sql = "SELECT uuid, username FROM players WHERE username = ?" const rows = await database.query(sql, [username]) return rows[0] || null } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function getProfileByHistory(username, isoDate) { try { const sql = ` SELECT uuid, username FROM uuidToNameHistory WHERE username = ? AND (changedAt <= ? OR changedAt IS NULL) ORDER BY changedAt DESC LIMIT 1 ` const rows = await database.query(sql, [username, isoDate]) return rows[0] || null } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function getNameHistory(uuid) { try { const sql = ` SELECT username, changedAt FROM uuidToNameHistory WHERE uuid = ? ORDER BY changedAt ASC ` const rows = await database.query(sql, [uuid]) return rows } catch (error) { logger.log("Database Error: " + error.toString(), ["MariaDB", "red"]) throw new DefaultError(500, "Internal Server Error", "Database Error") } } async function setSkin(uuid, hash, variant) { const insertSql = ` INSERT INTO playersSkins (playerUuid, assetHash, variant, isSelected) VALUES (?, ?, ?, 1) ON DUPLICATE KEY UPDATE isSelected = 1, variant = ? ` await database.query(insertSql, [uuid, hash, variant, variant]) const updateSql = ` UPDATE playersSkins SET isSelected = 0 WHERE playerUuid = ? AND assetHash != ? ` await database.query(updateSql, [uuid, hash]) return true } module.exports = { setSkin, banUser, showCape, hideCape, resetSkin, isBlocked, unbanUser, blockPlayer, createTexture, getPlayerBans, getPlayerMeta, unblockPlayer, changeUsername, getNameHistory, getBlockedUuids, getUsersByNames, getPlayerActions, getTextureByUuid, getTextureByHash, addProfileAction, getLastNameChange, getPlayerProperty, getUuidAndUsername, checkCapeOwnership, getProfileByHistory, getPlayerPrivileges, getPlayerProperties, removeProfileAction, addPropertyToPlayer, getProfileByUsername, getPlayerPreferences, getPlayerCertificate, clearAllPlayerActions, savePlayerCertificate, updatePlayerPrivileges, deletePropertyToPlayer, updatePropertyToPlayer, getPlayerSettingsSchema, updatePlayerPreferences, deleteExpiredCertificates, }