confirmed ban evasion cases stay closed

This commit is contained in:
MineTec committed 2026-10-14 17:16:13 +02:00
1 parent 66dace7f2a
commit 974c5c5200
2 files changed
+36 -15

No files matched your search

@@ -70,7 +70,7 @@ final class BanEvasionRepository {
record Member(UUID uuid, String name) { record Member(UUID uuid, String name) {
} }
record Household(long id, @Nullable String note, List<Member> members) { record Household(long id, Instant createdAt, @Nullable String createdByName, @Nullable String note, List<Member> members) {
} }
record Exemption(long id, String network, Instant validFrom, Instant validUntil, @Nullable String createdByName, @Nullable String note) { record Exemption(long id, String network, Instant validFrom, Instant validUntil, @Nullable String createdByName, @Nullable String note) {
@@ -156,18 +156,20 @@ final class BanEvasionRepository {
@Nullable CaseDecision.ExistingCase findCase(Connection connection, UUID first, UUID second) throws SQLException { @Nullable CaseDecision.ExistingCase findCase(Connection connection, UUID first, UUID second) throws SQLException {
long id; long id;
CaseStatus status; CaseStatus status;
try(PreparedStatement statement = connection.prepareStatement("SELECT id, status FROM ban_evasion_cases WHERE uuid_a = ? AND uuid_b = ?")) { String outcome;
try(PreparedStatement statement = connection.prepareStatement("SELECT id, status, outcome FROM ban_evasion_cases WHERE uuid_a = ? AND uuid_b = ?")) {
statement.setString(1, first.toString()); statement.setString(1, first.toString());
statement.setString(2, second.toString()); statement.setString(2, second.toString());
try(ResultSet row = statement.executeQuery()) { try(ResultSet row = statement.executeQuery()) {
if(!row.next()) return null; if(!row.next()) return null;
id = row.getLong("id"); id = row.getLong("id");
status = CaseStatus.valueOf(row.getString("status")); status = CaseStatus.valueOf(row.getString("status"));
outcome = row.getString("outcome");
} }
} }
Set<Reason> reasons = EnumSet.noneOf(Reason.class); Set<Reason> reasons = EnumSet.noneOf(Reason.class);
this.reasonsOf(connection, id).forEach(reason -> reasons.add(reason.reason())); this.reasonsOf(connection, id).forEach(reason -> reasons.add(reason.reason()));
return new CaseDecision.ExistingCase(id, status, reasons); return new CaseDecision.ExistingCase(id, status, outcome == null ? null : Outcome.valueOf(outcome), reasons);
} }
/** /**
@@ -458,21 +460,38 @@ final class BanEvasionRepository {
} }
} }
/**
* All households, newest first.
*/
List<Household> listHouseholds(Connection connection) throws SQLException { List<Household> listHouseholds(Connection connection) throws SQLException {
return this.households(connection, "", null);
}
@Nullable Household household(Connection connection, long id) throws SQLException {
List<Household> households = this.households(connection, "WHERE h.id = ?", id);
return households.isEmpty() ? null : households.getFirst();
}
private List<Household> households(Connection connection, String where, @Nullable Long id) throws SQLException {
String sql = """ String sql = """
SELECT h.id, h.note, m.uuid, %s AS name SELECT h.id, h.created_at, h.note, %s AS created_by_name, m.uuid, %s AS name
FROM ban_evasion_households h FROM ban_evasion_households h
JOIN ban_evasion_household_members m ON m.household_id = h.id JOIN ban_evasion_household_members m ON m.household_id = h.id
ORDER BY h.id, name %s
""".formatted(NAME_OF.formatted("m.uuid")); ORDER BY h.id DESC, name
""".formatted(NAME_OF.formatted("h.created_by"), NAME_OF.formatted("m.uuid"), where);
Map<Long, Household> households = new LinkedHashMap<>(); Map<Long, Household> households = new LinkedHashMap<>();
try(PreparedStatement statement = connection.prepareStatement(sql); ResultSet row = statement.executeQuery()) { try(PreparedStatement statement = connection.prepareStatement(sql)) {
while(row.next()) { if(id != null) statement.setLong(1, id);
long id = row.getLong("id"); try(ResultSet row = statement.executeQuery()) {
String householdNote = row.getString("note"); while(row.next()) {
UUID uuid = UUID.fromString(row.getString("uuid")); Instant createdAt = fromDb(row, "created_at");
households.computeIfAbsent(id, key -> new Household(key, householdNote, new ArrayList<>())) String createdByName = row.getString("created_by_name");
.members().add(new Member(uuid, nameOr(row.getString("name"), uuid))); String householdNote = row.getString("note");
UUID uuid = UUID.fromString(row.getString("uuid"));
households.computeIfAbsent(row.getLong("id"), key -> new Household(key, createdAt, createdByName, householdNote, new ArrayList<>()))
.members().add(new Member(uuid, nameOr(row.getString("name"), uuid)));
}
} }
} }
return List.copyOf(households.values()); return List.copyOf(households.values());
@@ -7,10 +7,11 @@ import java.util.Set;
/** /**
* Deduplication: one case per pair; admins are only notified about new information. * Deduplication: one case per pair; admins are only notified about new information.
* A closed case reopens only for a new reason of severity HIGH. * A closed case reopens only for a new reason of severity HIGH, a case closed as confirmed evasion never:
* further attempts of the banned person are expected there and only recorded.
*/ */
final class CaseDecision { final class CaseDecision {
record ExistingCase(long id, CaseStatus status, Set<Reason> reasons) { record ExistingCase(long id, CaseStatus status, @Nullable Outcome outcome, Set<Reason> reasons) {
} }
/** /**
@@ -35,6 +36,7 @@ final class CaseDecision {
.anyMatch(reason -> !reason.informational && finding.reasons().get(reason) == Severity.HIGH); .anyMatch(reason -> !reason.informational && finding.reasons().get(reason) == Severity.HIGH);
if(existing.status() == CaseStatus.OPEN) return new Result(false, newReasons, newRelevant); if(existing.status() == CaseStatus.OPEN) return new Result(false, newReasons, newRelevant);
if(existing.outcome() == Outcome.CONFIRMED) return new Result(false, newReasons, false);
return new Result(newHigh, newReasons, newHigh); return new Result(newHigh, newReasons, newHigh);
} }
} }