Add player action logging for admin operations

Introduces a new addPlayerAction method in adminRepository and logPlayerAction in adminService to record admin actions on player accounts. Updates relevant admin routes to log actions such as bans, unbans, forced name changes, and skin resets. Also improves error messages in adminService for consistency and clarity.
This commit is contained in:
Gilles Lazures 2026-01-05 05:06:06 +01:00
parent 439094013d
commit bfad2a39c1
5 changed files with 43 additions and 11 deletions

View File

@ -119,10 +119,28 @@ async function getAdminByUsername(username) {
} }
} }
async function addPlayerAction(playerUuid, actionCode) {
try {
const cleanUuid = playerUuid.replace(/-/g, "")
const sql = "INSERT IGNORE INTO playerProfileActions (uuid, action) VALUES (?, ?)"
const result = await database.query(sql, [cleanUuid, actionCode])
return {
code: 200,
success: result.affectedRows > 0,
message: result.affectedRows > 0 ? "Action taken." : "Action already taken."
}
} catch (error) {
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
throw new DefaultError(500, "Internal Server Error", "Database Error")
}
}
module.exports = { module.exports = {
createAdmin, createAdmin,
getAdminById, getAdminById,
hasPermission, hasPermission,
addPlayerAction,
assignPermission, assignPermission,
revokePermission, revokePermission,
getAdminByUsername, getAdminByUsername,

View File

@ -4,28 +4,34 @@ const userService = require("../../../services/userService")
const adminService = require("../../../services/adminService") const adminService = require("../../../services/adminService")
router.get("/:uuid", adminService.hasPermission("PLAYER_BAN_STATUS"), async (req, res) => { router.get("/:uuid", adminService.hasPermission("PLAYER_BAN_STATUS"), async (req, res) => {
const banStatus = await userService.getPlayerBanStatus(req.params.uuid) const { uuid } = req.params
const banStatus = await userService.getPlayerBanStatus(uuid)
return res.status(200).json(banStatus) return res.status(200).json(banStatus)
}) })
router.get("/:uuid/actions", adminService.hasPermission("PLAYER_ACTIONS_LIST"), async (req, res) => { router.get("/:uuid/actions", adminService.hasPermission("PLAYER_ACTIONS_LIST"), async (req, res) => {
const playerActions = await userService.getPlayerActions(req.params.uuid) const { uuid } = req.params
const playerActions = await userService.getPlayerActions(uuid)
return res.status(200).json(playerActions) return res.status(200).json(playerActions)
}) })
router.get("/:uuid/history", adminService.hasPermission("PLAYER_BAN_HISTORY"), async (req, res) => { router.get("/:uuid/history", adminService.hasPermission("PLAYER_BAN_HISTORY"), async (req, res) => {
const banHistory = await userService.getPlayerBans(req.params.uuid) const { uuid } = req.params
const banHistory = await userService.getPlayerBans(uuid)
return res.status(200).json(banHistory) return res.status(200).json(banHistory)
}) })
router.put("/:uuid", adminService.hasPermission("PLAYER_BAN"), async (req, res) => { router.put("/:uuid", adminService.hasPermission("PLAYER_BAN"), async (req, res) => {
const { reasonKey, reasonMessage, expires } = req.body const { reasonKey, reasonMessage, expires } = req.body
const ban = await userService.banUser(req.params.uuid, { reasonKey, reasonMessage, expires }) const ban = await userService.banUser(uuid, { reasonKey, reasonMessage, expires })
await adminService.logPlayerAction("BAN")
return res.status(200).json(ban) return res.status(200).json(ban)
}) })
router.delete("/:uuid", adminService.hasPermission("PLAYER_UNBAN"), async (req, res) => { router.delete("/:uuid", adminService.hasPermission("PLAYER_UNBAN"), async (req, res) => {
const ban = await userService.unbanUser(req.params.uuid) const { uuid } = req.params
const ban = await userService.unbanUser(uuid)
await adminService.logPlayerAction("UNBAN")
return res.status(200).json(ban) return res.status(200).json(ban)
}) })

View File

@ -5,6 +5,7 @@ const adminService = require("../../../services/adminService")
router.delete("/skin/:uuid", adminService.hasPermission("RESET_PLAYER_SKIN"), async (req, res) => { router.delete("/skin/:uuid", adminService.hasPermission("RESET_PLAYER_SKIN"), async (req, res) => {
const result = await userService.resetSkin(req.params.uuid) const result = await userService.resetSkin(req.params.uuid)
await adminService.logPlayerAction("USING_BANNED_SKIN")
return res.status(200).json(result) return res.status(200).json(result)
}) })

View File

@ -6,6 +6,7 @@ const adminService = require("../../../services/adminService")
router.patch("/:uuid", adminService.hasPermission("CHANGE_PLAYER_USERNAME"), async (req, res) => { router.patch("/:uuid", adminService.hasPermission("CHANGE_PLAYER_USERNAME"), async (req, res) => {
const { newUsername } = req.body const { newUsername } = req.body
const result = await userService.changeUsername(req.params.uuid, newUsername) const result = await userService.changeUsername(req.params.uuid, newUsername)
await adminService.logPlayerAction("FORCED_NAME_CHANGE")
return res.status(200).json(result) return res.status(200).json(result)
}) })

View File

@ -85,7 +85,7 @@ function hasPermission(requiredPermission) {
try { try {
const authHeader = req.headers.authorization const authHeader = req.headers.authorization
if (!authHeader || !authHeader.startsWith("Bearer ")) { if (!authHeader || !authHeader.startsWith("Bearer ")) {
throw new DefaultError(401, "Authentification admin requise.") throw new DefaultError(401, "Admin auth required.")
} }
const token = authHeader.split(" ")[1] const token = authHeader.split(" ")[1]
@ -117,29 +117,35 @@ async function uploadCape(fileObject, alias = null) {
const hash = crypto.createHash("sha256").update(buffer).digest("hex") const hash = crypto.createHash("sha256").update(buffer).digest("hex")
const existing = await userRepository.getTextureByHash(hash) const existing = await userRepository.getTextureByHash(hash)
if (existing) throw new DefaultError(409, "Cette cape existe déjà.") if (existing) throw new DefaultError(409, "Cape already existing.")
const textureUrl = `/texture/${hash}` const textureUrl = `/texture/${hash}`
await userRepository.createTexture(crypto.randomUUID(), hash, 'CAPE', textureUrl, alias) await userRepository.createTexture(crypto.randomUUID(), hash, "CAPE", textureUrl, alias)
return { hash, url: textureUrl } return { hash, url: textureUrl }
} }
async function deleteGlobalCape(hash) { async function deleteGlobalCape(hash) {
const success = await userRepository.deleteTexture(hash) const success = await userRepository.deleteTexture(hash)
if (!success) throw new DefaultError(404, "Cape introuvable.") if (!success) throw new DefaultError(404, "Cape not found.")
return { message: "Texture supprimée globalement." } return { message: "Texture removed." }
}
async function logPlayerAction(playerUuid, actionCode) {
return await adminRepository.addPlayerAction(playerUuid, actionCode)
} }
module.exports = { module.exports = {
loginAdmin, loginAdmin,
uploadCape, uploadCape,
registerAdmin, registerAdmin,
hasPermission,
getAdminProfile, getAdminProfile,
grantPermission, grantPermission,
revokePermission, revokePermission,
checkAdminAccess, checkAdminAccess,
deleteGlobalCape,
logPlayerAction,
changeAdminPassword, changeAdminPassword,
hasPermission,
} }