Skip to content

v2.2.0 (rc. 1)#17

Merged
byteful merged 16 commits intomainfrom
dev
Apr 6, 2026
Merged

v2.2.0 (rc. 1)#17
byteful merged 16 commits intomainfrom
dev

Conversation

@byteful
Copy link
Copy Markdown
Member

@byteful byteful commented Apr 1, 2026

No description provided.

Comment on lines +499 to +501
"bulk set expiration", (ctx) -> {
Map<String, Object> modification = buildModification(
"MANUAL_DURATION_CHANGE", ctx.now, performerUsername, reason, effectiveDuration);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 NPE when newDurationMs is zero or negative

Map.of() does not permit null values and will throw NullPointerException at runtime whenever newDurationMs <= 0, because effectiveDuration is set to null in that branch and then passed directly to Map.of("duration", effectiveDuration). The controller only guards against null, not against non-positive values, so a caller sending newDurationMs: 0 will trigger this crash.

Suggested change
"bulk set expiration", (ctx) -> {
Map<String, Object> modification = buildModification(
"MANUAL_DURATION_CHANGE", ctx.now, performerUsername, reason, effectiveDuration);
auditRepository.appendPunishmentModificationWithData(
server, ctx.playerId, ctx.punishmentId, modification,
effectiveDuration != null ? Map.of("duration", effectiveDuration) : Map.of());

Fix in Claude Code

Comment on lines +199 to +202

Object legacyEmail = request.formData().containsKey("contact_email")
? request.formData().get("contact_email")
: request.formData().get("email");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Unchecked cast causes ClassCastException on non-string input

x.get("content") returns Object. If a caller sends JSON like {"content": 123}, Jackson deserialises it as an Integer inside the Map<String, Object>, and the hard cast (String) throws ClassCastException, resulting in an unhandled 500 instead of a proper validation error.

Suggested change
Object legacyEmail = request.formData().containsKey("contact_email")
? request.formData().get("contact_email")
: request.formData().get("email");
Object rawContent = x.get("content");
String content = rawContent instanceof String s ? s : (rawContent != null ? rawContent.toString() : null);

Fix in Claude Code

Comment thread migrations/001-validate-punishment-data-schema.mongosh.js Outdated
Comment on lines +467 to +472
public int bulkPardonByType(
Server server, List<Integer> typeOrdinals, String reason, String performerUsername) {
return processBulkPunishmentAction(server, typeOrdinals, reason, performerUsername,
"bulk pardon", (ctx) -> {
if (AuditDocumentUtil.hasModificationType(ctx.punishmentDoc, "MANUAL_PARDON", "APPEAL_ACCEPT", "SYSTEM_PARDON")) {
return false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Raw string literals instead of PunishmentModificationType enum

The rest of the PR uses PunishmentModificationType.XYZ.name() (e.g. in StaffPerformanceService and AuditDocumentUtil calls), but this pardon check passes bare strings. If an enum value is ever renamed the check silently stops working.

Suggested change
public int bulkPardonByType(
Server server, List<Integer> typeOrdinals, String reason, String performerUsername) {
return processBulkPunishmentAction(server, typeOrdinals, reason, performerUsername,
"bulk pardon", (ctx) -> {
if (AuditDocumentUtil.hasModificationType(ctx.punishmentDoc, "MANUAL_PARDON", "APPEAL_ACCEPT", "SYSTEM_PARDON")) {
return false;
if (AuditDocumentUtil.hasModificationType(ctx.punishmentDoc,
PunishmentModificationType.MANUAL_PARDON.name(),
PunishmentModificationType.APPEAL_ACCEPT.name(),
PunishmentModificationType.SYSTEM_PARDON.name())) {

Fix in Claude Code

Comment on lines 233 to 241
issuerMatch.add(Criteria.where(PunishmentFields.ISSUER_ID).is(staffId));
}

Query query = Query.query(MongoQueries.where(PlayerFields.PUNISHMENTS).elemMatch(
Query query = Query.query(Criteria.where(PlayerFields.PUNISHMENTS).elemMatch(
new Criteria().orOperator(issuerMatch.toArray(new Criteria[0]))
));
query.fields().include(PlayerFields.ID, PlayerFields.MINECRAFT_UUID, PlayerFields.PUNISHMENTS);

return tenantMongoAccess.forServer(server).find(query, Document.class, CollectionName.PLAYERS);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 findPlayersForBulkAction fetches complete player documents with no field projection

For popular punishment types this query can return thousands of full player records into application memory at once. Only _id, usernames, and punishments are actually consumed. Adding a projection avoids loading full documents for every matched player.

Suggested change
issuerMatch.add(Criteria.where(PunishmentFields.ISSUER_ID).is(staffId));
}
Query query = Query.query(MongoQueries.where(PlayerFields.PUNISHMENTS).elemMatch(
Query query = Query.query(Criteria.where(PlayerFields.PUNISHMENTS).elemMatch(
new Criteria().orOperator(issuerMatch.toArray(new Criteria[0]))
));
query.fields().include(PlayerFields.ID, PlayerFields.MINECRAFT_UUID, PlayerFields.PUNISHMENTS);
return tenantMongoAccess.forServer(server).find(query, Document.class, CollectionName.PLAYERS);
public List<Document> findPlayersForBulkAction(Server server, List<Integer> typeOrdinals) {
Query query = Query.query(Criteria.where(PlayerFields.PUNISHMENTS).elemMatch(
Criteria.where(PunishmentFields.TYPE_ORDINAL).in(typeOrdinals)
));
query.fields()
.include(PlayerFields.ID, PlayerFields.MINECRAFT_UUID,
PlayerFields.USERNAMES, PlayerFields.PUNISHMENTS);
return tenantMongoAccess.forServer(server).find(query, Document.class, CollectionName.PLAYERS);
}

Fix in Claude Code

@modl-gg modl-gg deleted a comment from greptile-apps Bot Apr 2, 2026
@byteful
Copy link
Copy Markdown
Member Author

byteful commented Apr 6, 2026

@greptileai

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 6, 2026

Greptile Summary

This large PR (v2.2.0-rc.1, ~330 files) is primarily a package reorganization moving classes to infrastructure.* namespaces, extracting StaffPerformanceService from AuditService, introducing PunishmentMongoRepository, and adding two bulk punishment action endpoints (bulk pardon / bulk set expiration). The PR also introduces the PunishmentModificationType enum and consistently uses it throughout — except in the two new bulk-action methods, which still pass raw string literals where enum constants are now available.

Confidence Score: 4/5

Safe to merge after fixing the raw string literal issues in the two new bulk-action methods.

Two P1 findings exist in the newly added bulkPardonByType and bulkSetExpirationByType methods: raw string literals are used where the PunishmentModificationType enum constants (introduced in this same PR) should be referenced. This means if enum values are renamed, the already-pardoned detection silently breaks and duplicate pardons become possible. The remaining findings are P2 (missing max constraint on typeOrdinals and potential NPE in storage aggregation). The large-scale package reorganization and service extractions are cleanly done.

src/main/java/gg/modl/backend/audit/service/AuditService.java (P1 raw strings in bulk action methods); src/main/java/gg/modl/backend/audit/dto/request/BulkPunishmentActionRequest.java (missing max size); src/main/java/gg/modl/backend/database/mongo/repository/StorageFileMongoRepository.java (NPE potential)

Important Files Changed

Filename Overview
src/main/java/gg/modl/backend/audit/service/AuditService.java Bulk pardon/expiration methods use raw string literals for modification types instead of the newly introduced PunishmentModificationType enum constants (P1)
src/main/java/gg/modl/backend/audit/service/StaffPerformanceService.java Clean extraction of staff performance and details logic from AuditService into its own service; correctly uses enum constants
src/main/java/gg/modl/backend/audit/service/AuditDocumentUtil.java New package-private utility extracting document parsing helpers, reducing duplication between AuditService and StaffPerformanceService
src/main/java/gg/modl/backend/database/mongo/repository/PunishmentMongoRepository.java New repository consolidating punishment DB operations from PlayerMongoRepository; correct field projections on most queries
src/main/java/gg/modl/backend/database/mongo/repository/AuditMongoRepository.java Replaced MongoQueries wrappers with direct Criteria calls; added projections to findAllStaff and mapStaffUsernamesByIds; findPlayersForBulkAction still lacks projection
src/main/java/gg/modl/backend/infrastructure/filter/PanelPermissionFilter.java New centralized filter enforcing panel permissions per endpoint path and HTTP method; super-admin bypass is correctly handled
src/main/java/gg/modl/backend/infrastructure/filter/AdminAuthFilter.java Moved to infrastructure package; now correctly returns 401 when admin session is absent instead of passing unauthenticated requests through
src/main/java/gg/modl/backend/player/data/punishment/PunishmentModificationType.java New enum defining all modification type constants with an isPardon() convenience helper
src/main/java/gg/modl/backend/player/data/punishment/PunishmentData.java New utility providing type-safe accessors for punishment data map fields, eliminating ad-hoc casts throughout the codebase
src/main/java/gg/modl/backend/audit/controller/AuditController.java New bulk-pardon and bulk-set-expiration endpoints properly gated behind a super-admin permission check
src/main/java/gg/modl/backend/audit/dto/request/BulkPunishmentActionRequest.java typeOrdinals list lacks an upper-bound @SiZe constraint, enabling unbounded bulk operations against the full player collection
src/main/java/gg/modl/backend/database/mongo/repository/StorageFileMongoRepository.java New storage file repository; aggregation result handling has potential NPE if totalSize field is absent in the BSON document
src/main/java/gg/modl/backend/settings/service/PunishmentTypeIndex.java New utility building an ordinal-keyed map for O(1) punishment type lookup, replacing O(n) stream searches
src/main/java/gg/modl/backend/player/service/PunishmentDurationCalculator.java Clean extraction of duration calculation logic from PunishmentLifecycleService into a dedicated service

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[POST /punishments/bulk-pardon\nor /bulk-set-expiration] --> B[AuditController]
    B --> C{requireSuperAdmin?}
    C -- No --> D[403 Forbidden]
    C -- Yes --> E[AuditService.bulkPardonByType\nor bulkSetExpirationByType]
    E --> F[processBulkPunishmentAction]
    F --> G[findPlayersForBulkAction\nAuditMongoRepository]
    G --> H[Iterate Players]
    H --> I[Iterate Punishments]
    I --> J{typeOrdinal\nin typeOrdinals?}
    J -- No --> I
    J -- Yes --> K{isPunishmentActive?}
    K -- No --> I
    K -- Yes --> L{BulkPunishmentAction.apply}
    L -- Pardon --> M[appendPunishmentModification\nstatus=Pardoned\ncascadeLinkedBans if altBlocking]
    L -- SetExpiration --> N[appendPunishmentModification\nduration=newDurationMs]
    M --> O[saveAuditLog]
    N --> O
    O --> I
    I --> P[Return count]
Loading

Fix All in Claude Code

Greploops — Automatically fix all review issues by running /greploops in Claude Code. It iterates: fix, push, re-review, repeat until 5/5 confidence.
Use the Greptile plugin for Claude Code to query reviews, search comments, and manage custom context directly from your terminal.

Reviews (2): Last reviewed commit: "Merge branch 'main' into dev" | Re-trigger Greptile

@byteful byteful merged commit 77f93c3 into main Apr 6, 2026
2 checks passed
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.

2 participants