Skip to content

Staff management System#186

Open
Kevinking500 wants to merge 12 commits intoScootKit:mainfrom
Kevinking500:staff-management-system
Open

Staff management System#186
Kevinking500 wants to merge 12 commits intoScootKit:mainfrom
Kevinking500:staff-management-system

Conversation

@Kevinking500
Copy link
Copy Markdown
Contributor

Staff Management System: a new module designed for management, by management

(Idk that sounded cool)

This PR introduces the Staff Management System, which directly also correlates to Seppe's suggestion.

Features in this module:

  • Infractions with custom infraction types
  • Promotions
  • Activity checks, with automation possibilities and results.
  • Staff profiles, which also show some status information autmatically (such as LoA/RA)
  • Leave of Abscence and Reduced Activity status - Staff can request an LoA/RA for a specified duration, and the bot configurator can also set a custom max duration for each type.
  • Reviews system: Users can now review staff, or everyone if enabled, without the need of custom commands. This feature also includes a history command, which calculates your average rating and you can view all reviews history.
  • Shifts system: Staff can go on-duty, on break or go off-duty again. Shift time gets saved, and set to a staff shfit leaderboard, which the bot configurator can set a custom timeframe to see it. Additionally, this feature also includes custom shift types.
  • Centralized User Panel: Just like in PP, this module includes a user panel, but improved with a dropdown for each feature, and each feature includes small extra details that normal embeds do not show. This dropdown also includes the data deletion side, which has been reworked from PP to be more clear, and when deleting all data for the user, it also includes a small timed second confirmation. This has been done since this is done by staff users, and not the bot configurator directly in the dashboard.

This time, unlike the PP module, data will be kept until deleted manually by management roles and above. Though this might be changed.

AI usage in this module:

  1. Gemini: Some debugging and small optimizations
  2. Claude: Some debugging when Gemini wasn't able to fix it

SCDerox and others added 10 commits January 9, 2026 14:33
* Added the base module folders and module.json

* Added all folders necessary and the configuration files for each folder

* Added a test command

* removed the manage file

* Added, renamed and deleted some files as necessary and coded the models.

* Renamed action.js to moderation.js, coded multiple things, added a new file for correct tracking.

* Forgot to update module.json, now updated aswell

* Added additional information in ping-protection.js

* Disabled allowing reply pings, added the enable moderation and enable advanced configuration in moderation.json and made the choices inside depend on it because I forgot to :/ Added the options to enable/disable pings/modlogs/logs kept after leave and made the choices depend on it + made those choices with numbers select instead of integers for almost 0 user-error issues.

* Added support for actually correct parameters and those parameters added into the message editor for the warning message

* Added proper support for localization, and coded the events

* Completed the full module and fixed some critical bugs that caused the bot to crash

* Cleaned up some code notes I used for debugging

* Completely finished the module and worked tirelessly for many hours to debug code, has been tested and is currently ongoing extensive testing to ensure absolutely everythig works as supposed to

* Debugged absolutely everything, removed like 300 lines of code for polish while remaining the same functions. Removed a few locales that are unused and updated some locales for better understanding. Fully tested extensively. Not verified by GitHub because I code in VSCode.

* Added the option to lower mod actions history

* Made the deault value of pings to trigger action 10 instead of 5 in basic pings count config

* Added the commands warnings for most commands

Listed the warnings for all commands except the panel command as the bot already checks for administrator perms.

* Almost completely rewrote the module to make sure the modules works as supposed to with SCNX.

* Added "automod" abilities - Will now delete the original message by default with the message content and allows to configure both options

* (not working correctly) added automod integration and some small changes

* Fixed the

* Removed the feature that didn't work (reposting), adds a custom message to the automod message block. Also the bot now deletes the rule it created if automod enabled = false

* Fixed the bug of the bot still sending the warning and punishing if limit reached with reply pings even when it's allowed in config

* Added a funny easter egg

* Some QOL improvements, including merging the list commands

* Added some new options  in the config

* Update configuration.json

* Fix self-ping condition to allow self-pinging

* Ping protection V1, in Discord.JS V14

* Ping Protection V1

* Changed code to the requested changes, and adjusted code logic to actually properly support multiple moderation actions (not tested before, and didn't work during testing)

* Made adjustments to code as requested, and added an intent to main.js to make it work properly.

* Fixed the missing footer on embeds. Fixed a small typo in the default waning message configuration and added an emoji to the why easter egg.

---------

Co-authored-by: Kevinking500 <Kevinking500>
* Added the base module folders and module.json

* Added all folders necessary and the configuration files for each folder

* Added a test command

* removed the manage file

* Added, renamed and deleted some files as necessary and coded the models.

* Renamed action.js to moderation.js, coded multiple things, added a new file for correct tracking.

* Forgot to update module.json, now updated aswell

* Added additional information in ping-protection.js

* Disabled allowing reply pings, added the enable moderation and enable advanced configuration in moderation.json and made the choices inside depend on it because I forgot to :/ Added the options to enable/disable pings/modlogs/logs kept after leave and made the choices depend on it + made those choices with numbers select instead of integers for almost 0 user-error issues.

* Added support for actually correct parameters and those parameters added into the message editor for the warning message

* Added proper support for localization, and coded the events

* Completed the full module and fixed some critical bugs that caused the bot to crash

* Cleaned up some code notes I used for debugging

* Completely finished the module and worked tirelessly for many hours to debug code, has been tested and is currently ongoing extensive testing to ensure absolutely everythig works as supposed to

* Debugged absolutely everything, removed like 300 lines of code for polish while remaining the same functions. Removed a few locales that are unused and updated some locales for better understanding. Fully tested extensively. Not verified by GitHub because I code in VSCode.

* Added the option to lower mod actions history

* Made the deault value of pings to trigger action 10 instead of 5 in basic pings count config

* Added the commands warnings for most commands

Listed the warnings for all commands except the panel command as the bot already checks for administrator perms.

* Almost completely rewrote the module to make sure the modules works as supposed to with SCNX.

* Added "automod" abilities - Will now delete the original message by default with the message content and allows to configure both options

* (not working correctly) added automod integration and some small changes

* Fixed the

* Removed the feature that didn't work (reposting), adds a custom message to the automod message block. Also the bot now deletes the rule it created if automod enabled = false

* Fixed the bug of the bot still sending the warning and punishing if limit reached with reply pings even when it's allowed in config

* Added a funny easter egg

* Some QOL improvements, including merging the list commands

* Added some new options  in the config

* Update configuration.json

* Fix self-ping condition to allow self-pinging

* Ping protection V1, in Discord.JS V14

* Ping Protection V1

* Changed code to the requested changes, and adjusted code logic to actually properly support multiple moderation actions (not tested before, and didn't work during testing)

* Made adjustments to code as requested, and added an intent to main.js to make it work properly.

* Fixed the missing footer on embeds. Fixed a small typo in the default waning message configuration and added an emoji to the why easter egg.

* Ping Protection V1.1

* Quickly updated locales for better explanation about exceptions for whitelisted

* Ping Protection V1.1 remastered

* Another remastered version

* Added categories in 2 configs

* Used proper emoji's that SCNX supports

* Used the new updated ones I suggested that were added (and hopefully also work)

* Updated some small changes from Copilot

---------

Co-authored-by: Kevinking500 <Kevinking500>
* feat(economy): implements "Artikel bearbeiten"
Implements https://featureboard.net/suggestions/95f5a741-6d35-4b86-9c67-abd900dca76e

* fix(economy): Adressed the issues copilot pointed out and also fixed them in createShopItem
* Weird things are happening. Tried to rebuild it

* Weird things are happening. Tried to rebuild it

* Next try, please work this time

* temp-removed locales
@Kevinking500
Copy link
Copy Markdown
Contributor Author

"too complex to solve in the web edditor" what else am I supposed to do 😭

@Kevinking500
Copy link
Copy Markdown
Contributor Author

Oh, the latest commit for module name list cleaner temp removed locales which causes this..

@SCDerox SCDerox changed the base branch from beta-discordjs14 to main March 16, 2026 11:37
@SCDerox SCDerox requested a review from Copilot March 16, 2026 11:37
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new Staff Management System module that adds management workflows (infractions/suspensions, promotions, staff reviews, activity checks, LoA/RA status handling, and shift tracking) plus supporting configs, models, commands, events, and locale strings. It also includes a small core interaction handler tweak to support commands that use subcommand groups.

Changes:

  • Add the staff-management-system module: logic layer, commands, component interaction handlers, scheduled jobs, and Sequelize models.
  • Add dashboard config schemas for the module’s features (infractions, promotions, reviews, shifts, status, profiles, activity checks).
  • Extend localization (locales/en.json) with staff-management-system strings (and unrelated guess-the-number strings).

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/events/interactionCreate.js Treats SUB_COMMAND_GROUP as a subcommand container when dispatching slash commands.
modules/staff-management-system/staff-management.js Core logic for staff management features (infractions, promotions, reviews, statuses, panels, activity checks).
modules/staff-management-system/module.json Module metadata and config file registration.
modules/staff-management-system/models/ActivityCheck.js Sequelize model for activity check records.
modules/staff-management-system/models/Infraction.js Sequelize model for infractions/suspensions.
modules/staff-management-system/models/LoaRequest.js Sequelize model for LoA/RA requests.
modules/staff-management-system/models/Promotion.js Sequelize model for promotions.
modules/staff-management-system/models/StaffProfile.js Sequelize model for staff profile + duty/status fields.
modules/staff-management-system/models/StaffReview.js Sequelize model for staff reviews.
modules/staff-management-system/models/StaffShift.js Sequelize model for shift logs.
modules/staff-management-system/events/interactionCreate.js Component interaction routing for staff management UI, duty UI, LoA/RA actions, deletion.
modules/staff-management-system/events/guildMemberRemove.js Auto-closes open shifts when a member leaves.
modules/staff-management-system/events/botReady.js Schedules LoA/RA expiry jobs and checks expired suspensions periodically.
modules/staff-management-system/configs/configuration.json Base roles + general log channel config schema.
modules/staff-management-system/configs/infractions.json Infractions/suspensions config schema + templates.
modules/staff-management-system/configs/promotions.json Promotions config schema + templates.
modules/staff-management-system/configs/reviews.json Reviews config schema + templates.
modules/staff-management-system/configs/shifts.json Shifts/leaderboard/quota config schema.
modules/staff-management-system/configs/status.json LoA/RA status config schema + logging options.
modules/staff-management-system/configs/profiles.json Staff profiles config schema + template.
modules/staff-management-system/configs/activity-checks.json Activity check + automation config schema.
modules/staff-management-system/commands/staff-management.js Slash command entrypoint for most staff management actions.
modules/staff-management-system/commands/status.js Slash command for LoA/RA request/view/list/admin manage.
modules/staff-management-system/commands/duty.js Slash command + button handlers for duty/shift tracking UX.
locales/en.json Adds staff-management-system strings (and new guess-the-number strings).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Kevinking500
Copy link
Copy Markdown
Contributor Author

All changes have been fixed that Copilot mentioned here

@Kevinking500
Copy link
Copy Markdown
Contributor Author

Just a quick note, I am quite the bit less available and more busy the last few days, and would likely stay like this for roughly the next week(s) as I am preparing and planning for my trip next month ^^

@SCDerox SCDerox self-requested a review March 26, 2026 14:15
Copy link
Copy Markdown
Member

@SCDerox SCDerox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great PR! Some small rather important things:

@Kevinking500
Copy link
Copy Markdown
Contributor Author

Due to a sudden finding of a critical bug (duty time still being recorded even while being on break) during the testing after changes, the next commit will unfortunately be delayed by quite the bit until I fix this ^^

@Kevinking500
Copy link
Copy Markdown
Contributor Author

Actually, no big delay (I sent that fairly late, I was working for this for more than 1 hour now). Will be committing it shortly

@Kevinking500 Kevinking500 requested a review from SCDerox March 26, 2026 20:29
Copy link
Copy Markdown
Member

@SCDerox SCDerox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Found 9 issues (4 critical bugs, 1 security issue, 4 logic/data bugs):

  1. del_all confirmation bypassed — data deleted immediately without waiting for user confirmation (missing return after collector setup)
  2. interaction.update() called on ModalSubmitInteraction in LOA deny handler (method doesn't exist on modal submits)
  3. handleStatusEnd called with wrong arguments — client passed where interaction expected, crashes on every "End Early" button press
  4. interaction.update() called on ModalSubmitInteraction in duty admin add-time handler (same root cause as #2)
  5. Break recovery corrupts shift startTime on every bot restart by double-counting elapsed break time
  6. guildMemberRemove uses findOne instead of findAll — orphans multiple open shifts
  7. No authorization re-check on admin duty button handlers (force-end, void, add-time)
  8. initActivityCheckAutomation never called in botReady — automated checks never fire
  9. Suspension isSuspended: true upsert only runs conditionally — leaves inconsistent state

Generated with Claude Code

});
}

await executeDataDeletion(client, targetId, selection);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug (Critical): del_all confirmation bypassed — data deleted immediately without user confirmation.

When selection === 'del_all', the if block (lines 202-283) sets up a confirmation dialog with a button collector. But after that block ends at line 283, execution falls through to this line which calls executeDataDeletion unconditionally. This means:

  • Data is wiped immediately before the user clicks "Confirm"
  • interaction.reply() at line 297 crashes because it was already called at line 221
  • If the user then clicks Confirm, deletion runs a second time

Fix: Add return; after line 283 to prevent fall-through, or wrap lines 285-300 in an else block.

name: localize('staff-management-system', 'general-rsn'),
value: reason
});
return interaction.update({
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug (Critical): interaction.update() does not exist on ModalSubmitInteraction.

This handler runs on a modal submit (interaction.isModalSubmit() at line 421), but ModalSubmitInteraction in Discord.js v14 does not have an update() method — only MessageComponentInteraction does. This throws TypeError: interaction.update is not a function every time a supervisor denies a LOA/RA request.

Fix: Replace with interaction.reply() or use interaction.deferReply() + interaction.editReply().

const type = action.startsWith('loa-') ? 'LOA' : 'RA';
const base = action.replace(/^(loa|ra)-/, '');

if (base === 'end') return handleStatusEnd(client, interaction, type);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug (Critical): Wrong argument order — client passed where interaction expected.

handleStatusEnd is defined in status.js:467 as handleStatusEnd(interaction, type) (2 params), but here it is called with (client, interaction, type) (3 args). Inside the function, interaction receives the client object, so interaction.customId.split('_') throws immediately. The "End Early" button is completely non-functional.

Fix: Change to handleStatusEnd(interaction, type).

const targetMember = await interaction.guild.members.fetch(targetUserId).catch(() => null);
const payload = await buildDutyAdminPayload(client, targetMember, interaction.member);

return interaction.update(payload);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug (Critical): interaction.update() does not exist on ModalSubmitInteraction.

This handler is called for the duty-mgmt_admin-addtime-submit modal submission. ModalSubmitInteraction has no update() method — same issue as the LOA deny handler. Every admin attempt to add manual time crashes.

Fix: Replace with interaction.reply(payload) or interaction.editReply(payload) after deferring.

const elapsedBreakMs = now.getTime() - breakStartedAt.getTime();

await activeShift.update({
startTime: new Date(new Date(activeShift.startTime).getTime() + elapsedBreakMs)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Break recovery corrupts shift startTime on every bot restart.

This adjusts startTime forward by the elapsed break time, then resets breakStartTime to now at line 111 — but leaves onBreak: true. On the next restart, recoverActiveBreaks runs again, calculates elapsed time since the last restart, and pushes startTime forward again. Each restart accumulates the error. After multiple restarts, staff on break will have zero-duration or negative-duration shifts.

Fix: Either end the break after recovery (onBreak: false, breakStartTime: null) or don't reset breakStartTime to now.

const StaffProfile = client.models['staff-management-system']['StaffProfile'];

try {
const openShift = await StaffShift.findOne({
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: findOne only closes one open shift — remaining shifts are orphaned permanently.

If a user has multiple open shifts, only the first is closed. The rest remain with endTime: null forever. Also, onBreak and breakStartTime are not reset (line 30 only sets onDuty: false).

Fix: Change to findAll and loop, matching handleDutyAdminForceEnd (duty.js:614-631). Also reset onBreak/breakStartTime.

}

// ----- Admin handler -----
async function handleDutyAdminForceEnd(client, interaction) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security: No authorization re-check on admin duty button handlers.

The /duty admin slash command checks permissions, but handleDutyAdminForceEnd (607), handleDutyAdminVoidActive (649), handleDutyAdminVoidAll (676), and handleDutyAdminAddTimeSubmit (768) have no permission check. handleDutyAdminVoidAll is especially dangerous — it permanently destroys all shift records.

Fix: Add a permission guard at the top of each handleDutyAdmin* function.

const { Op } = require('sequelize');
const { scheduleStatusExpiry } = require('../commands/status.js');
const suspension_check_job = 'staff-management-checks';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: initActivityCheckAutomation is never imported or called — automated activity checks will never fire.

The function is exported from staff-management.js but never used here. Users who configure automated checks in activity-checks.json will see no checks run.

Fix: Import and call initActivityCheckAutomation(client) in this handler.

const durationString = `${durationDays} ${localize('staff-management-system', 'label-days')}`;

const hierarchyRole = interaction.guild.roles.cache.get(config.suspensionHierarchyRole);
if (hierarchyRole) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: isSuspended: true upsert nested inside if (rolesToRemove.length) — never set when no roles to remove.

If no suspensionHierarchyRole is configured or user has no matching roles, this block is skipped. The suspension record exists but isSuspended stays false. When the suspension expires, cleanup in checkExpiredSuspensions (botReady.js:161) skips because profile?.isSuspended is false — suspension role is never removed.

Fix: Move the upsert outside the if (rolesToRemove.length) guard.

Copy link
Copy Markdown
Member

@SCDerox SCDerox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Agentic Code Review

Hey Kevin, thanks for your contribution on the staff management system -- this is a big module and a lot of solid work!

Heads up: This is an automated agentic review test that Simon is running. An AI agent (Claude Code) independently reviewed every file in this PR using multiple parallel review passes (bug scanning, security audit, UX review, cross-file consistency, and code quality analysis). Each finding was then independently scored for confidence, and only issues scoring above the threshold were included.

Found 9 high-priority issues (from the previous review comment) and 15 lower-priority issues (marked as "Minor" in this review).

The high-priority ones include 4 critical runtime crashes (wrong method calls on modal submits, argument mismatch, missing return), 1 security concern (missing auth re-checks on admin handlers), and 4 data integrity bugs. The minor ones cover validation gaps, UX improvements, i18n issues, and edge cases.

Please feel free to:

  • Dismiss any findings that are wrong or not applicable -- automated reviews can produce false positives
  • Ask for clarification on any comment if the reasoning isn't clear
  • Provide feedback on whether this type of agentic review is useful, too noisy, or could be improved

Simon is testing this workflow and your feedback would be valuable for tuning it.

Thanks again for the contribution!

Generated with Claude Code

return interaction.showModal(modal);
}

async function handleStatusEndSubmit(client, interaction, type) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor (Security): No authorization re-check on handleStatusEndSubmit and handleStatusExtendSubmit modal handlers.

Same pattern as the duty admin button issue — the slash command checks supervisor permissions, but these modal submission handlers don't re-verify. A forged modal customId could bypass the UI gate.

}

// ----- Deny modal submission -----
if (interaction.isModalSubmit() && action === 'loa-deny') {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor (Security): No authorization re-check on loa-deny modal submission.

The deny button handler (line 356) checks isSupervisor before showing the modal, but this modal submit handler doesn't re-check. Request IDs are sequential integers, making them enumerable.

if (!config?.enableInfractions) return interaction.reply({
content: localize('staff-management-system', 'err-feat-disabled', { feature: 'Infractions' }),
flags: MessageFlags.Ephemeral
});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor (Validation): Infraction type not validated against configured types.

Discord autocomplete is advisory — users can submit arbitrary strings via the API. The code only rejects 'suspension' but doesn't validate against config.infractionTypes. An arbitrary type string gets stored in the DB and displayed in embeds.

const minutesRaw = interaction.fields.getTextInputValue('minutes');
const shiftType = interaction.fields.getTextInputValue('type');

const minutes = parseInt(minutesRaw);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor (Validation): No upper bound on admin add-time minutes.

A supervisor can enter 999999999 minutes (~1900 years), creating a shift record that dominates leaderboard calculations and quota stats. Consider adding a reasonable cap (e.g. 10080 minutes = 1 week).

}

// ----- Promotion history pagination -----
if (action === 'prom-hist') {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor (UX): No deferUpdate() before DB queries in pagination handlers.

The promotion history, infraction history, and review history pagination handlers (lines 70-120) perform DB queries before replying without calling deferUpdate() first. If the database is slow, this risks Discord's 3-second interaction timeout, showing "This interaction failed" to the user.


const oldEndDate = new Date(request.endDate);
const newEndDate = new Date(oldEndDate.getTime() + days * 24 * 60 * 60 * 1000);
await request.update({ endDate: newEndDate });
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor (Data): scheduleStatusExpiry may use stale in-memory endDate after extend.

After request.update({ endDate: newEndDate }), scheduleStatusExpiry(client, request) is called immediately. Depending on Sequelize version/config, the in-memory request.endDate may not reflect the new value, causing the expiry job to fire at the old (earlier) time.

Consider await request.reload() after the update.

},
type: {
type: DataTypes.STRING,
defaultValue: "General"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor (Data): Model default "General" conflicts with code fallback "Staff".

The model defines defaultValue: "General" but every fallback in duty.js uses 'Staff' (e.g. lines 141, 496, 534). Shifts inserted without an explicit type get "General" stored, but the UI and autocomplete only offer configured duty types (defaulting to ['Staff']). These shifts become invisible in per-type filtered queries.

}
}

if (dataType === 'del_all') {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor (Data): del_all doesn't clear isSuspended or suspendedRoles on the profile.

The "delete all" data wipe clears shifts, infractions, promotions, reviews, status requests, and activity data, but doesn't reset isSuspended or suspendedRoles. A suspended user whose data is wiped retains stale suspension state, causing checkExpiredSuspensions to attempt restoring roles from stale data.

flags: MessageFlags.Ephemeral
});

let responded = JSON.parse(activeCheck.respondedUsers || '[]');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor (Data): Race condition in activity check response recording.

The read-modify-write on respondedUsers (JSON text field) is non-atomic: parse JSON, check if user exists, push, write back. Two concurrent button clicks can both read the same state before either writes, silently dropping one response. Consider a separate ActivityCheckResponse table with a unique constraint, or a DB-level atomic update.

// ----- Gets infraction history -----
async function getInfractionHistory(client, interaction, targetUser) {
const response = await generateInfractionHistoryResponse(client, targetUser, 1);
if (response.content && response.content.startsWith('ℹ️')) return interaction.reply(response);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor (Fragile logic): Reply routing decided by emoji prefix of localized string.

This checks response.content.startsWith('ℹ️') to determine routing. If the locale string for the "clean record" key ever changes its prefix, this branch silently mis-routes. The same pattern exists at line 614 for promotion history. Consider using a separate flag or property instead of inspecting the localized string content.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants