Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
077d5ef
INIT
P1otrulla Sep 14, 2026
6e35c6d
Add a missing label
P1otrulla Sep 14, 2026
20ad6f4
Add controllers
P1otrulla Sep 14, 2026
535e885
🥲
P1otrulla Sep 14, 2026
b2e98bf
Add KickCommand and normally use Permissions
P1otrulla Sep 14, 2026
869bcb1
Add KickAll logic
P1otrulla Sep 14, 2026
24e6465
fix typo
P1otrulla Sep 14, 2026
f550305
Add ip saving system
P1otrulla Sep 15, 2026
2c7109c
IP system has been improved.
P1otrulla Sep 15, 2026
9466fc7
cleanUp
P1otrulla Sep 15, 2026
57adbc6
Add banned player join notification and IP evasion handling and fix s…
P1otrulla Sep 16, 2026
215d511
Add banned player join notification and IP evasion handling and fix s…
P1otrulla Sep 16, 2026
ba78e0e
Add punishment commands, warning escalations, and history pagination
P1otrulla Sep 16, 2026
ee95629
Make plugin run
CitralFlo Sep 16, 2026
67be256
Add logging for punishment commands and introduce `DurationReasonParser`
P1otrulla Sep 16, 2026
82d1bff
Merge remote-tracking branch 'origin/ficzer/punishmenty' into ficzer/…
P1otrulla Sep 16, 2026
7f63902
Refine punishment commands to exclude console operators from bypass p…
P1otrulla Sep 16, 2026
5cb0e90
Remove `UnbanIpCommand` and associated IP unban messages
P1otrulla Sep 16, 2026
11aa029
Add `docker-compose.yml` for PostgreSQL setup and remove extraneous n…
P1otrulla Sep 17, 2026
1d9fb7b
Introduce punishment history GUI with improved flexibility and IP unb…
P1otrulla Sep 17, 2026
8c6abe8
Add IP address validator, punishment expiration support, and new mute…
P1otrulla Sep 17, 2026
16256f1
Downgrade `INV_UI` dependency to version 2.3.2
P1otrulla Sep 17, 2026
2099101
Add duration support for warnings, enhance permissions, and refactor …
P1otrulla Sep 17, 2026
e54a449
Update punishment history GUI to include expiration details in entry …
P1otrulla Sep 17, 2026
c869dd2
Refactor punishment system: switch to Java records, simplify reposito…
P1otrulla Sep 20, 2026
ea90878
Update JOOQ dependency to version 3.21.8
P1otrulla Sep 20, 2026
8f95859
Remove xenondevs repository from eternalcore-repositories configuration
P1otrulla Sep 20, 2026
d580c05
Remove `InstantPersister`, simplify null validation in `AltAccount` a…
P1otrulla Sep 20, 2026
8430270
Remove JOOQ-related repositories and schemas, including IP punishment…
P1otrulla Sep 24, 2026
457ccf7
Increase `PLAYER_NAME_MAX_LENGTH` from 16 to 32 across relevant datab…
P1otrulla Sep 24, 2026
c228d0c
Refactor punishment system: unify punishment table structure, enhance…
P1otrulla Sep 26, 2026
92a3e4c
Enhance IP ban handling: add KnownPlayerResolver, improve UnbanComman…
P1otrulla Sep 26, 2026
d0d05b0
Remove reactive-streams and r2dbc-spi dependencies from build configu…
P1otrulla Sep 26, 2026
fafc37d
Remove `PunishmentExpireTask` as part of simplifying punishment syste…
P1otrulla Sep 26, 2026
382485f
Add punishment filtering by type to GUI and repository layers, refact…
P1otrulla Sep 26, 2026
aca0021
Update eternalcore-core/src/main/java/com/eternalcode/core/feature/pu…
P1otrulla Oct 4, 2026
d379bd1
Update eternalcore-core/src/main/java/com/eternalcode/core/ip/SecretK…
P1otrulla Oct 4, 2026
4052a7d
Update eternalcore-core/src/main/java/com/eternalcode/core/feature/pu…
P1otrulla Oct 4, 2026
660a851
Apply PR #1429 review: rename history service to repository, back to …
PietregTM Oct 4, 2026
60788bb
Rework punishments per review: separate domains, single event-log table
PietregTM Oct 4, 2026
2b38a36
Refactor punishment packages and defaults
P1otrulla Oct 4, 2026
5cd6715
Specify EternalCoreBroadcast generic types
P1otrulla Oct 4, 2026
33c6f37
final
P1otrulla Oct 4, 2026
b7cc926
Update WhoIsCommand.java
P1otrulla Oct 4, 2026
f759c75
Update eternalcore-core/src/main/java/com/eternalcode/core/feature/pu…
P1otrulla Oct 6, 2026
1d6d5ff
Update eternalcore-core/src/main/java/com/eternalcode/core/ip/SecretK…
P1otrulla Oct 6, 2026
10aabca
Update eternalcore-core/src/main/java/com/eternalcode/core/feature/wh…
P1otrulla Oct 6, 2026
029714c
Standardize IP ban naming to BanIp
P1otrulla Oct 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ object Versions {
const val GUAVA = "33.6.0-jre"
const val GSON = "2.14.0"

const val EXPRESSIBLE = "1.3.6"
const val PANDA_UTILITIES = "0.5.3-alpha"
const val APACHE_COMMONS = "2.22.0"

const val TRIUMPH_GUI = "3.1.13"
Expand Down
15 changes: 15 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
services:
postgres:
image: postgres:16
container_name: eternalcore-postgres
environment:
POSTGRES_DB: eternalcore
POSTGRES_USER: eternalcore
POSTGRES_PASSWORD: eternalcore
ports:
- "5432:5432"
volumes:
- eternalcore-postgres-data:/var/lib/postgresql/data

volumes:
eternalcore-postgres-data:
2 changes: 1 addition & 1 deletion eternalcore-api-example/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ bukkit {
}

java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
}

tasks.withType<JavaCompile> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.eternalcode.core.feature.punishment;

import java.time.Instant;
import java.util.Optional;
import java.util.UUID;

/**
* Common read-only view of every punishment domain (ban, ip ban, mute, warn, kick).
* Status is derived, never stored: see {@link PunishmentStatus#resolve(Instant, Instant, Instant)}.
*/
public interface Punishment {

UUID id();

PunishmentTarget target();

PunishmentTarget operator();

String reason();

Instant createdAt();

/**
* @return moment when punishment expires, null = permanent
*/
Instant expiresAt();

/**
* @return revocation details, null = not revoked
*/
Revocation revocation();

default Optional<Instant> expiresAtOptional() {
return Optional.ofNullable(this.expiresAt());
}

default Optional<Revocation> revocationOptional() {
return Optional.ofNullable(this.revocation());
}

default boolean isPermanent() {
return this.expiresAt() == null;
}

default PunishmentStatus status(Instant now) {
Revocation revocation = this.revocation();
Instant revokedAt = revocation == null ? null : revocation.revokedAt();

return PunishmentStatus.resolve(this.expiresAt(), revokedAt, now);
}

default PunishmentStatus status() {
return this.status(Instant.now());
}

default boolean isActive(Instant now) {
return this.status(now) == PunishmentStatus.ACTIVE;
}

default boolean isActive() {
return this.isActive(Instant.now());
}

/**
* Fail-fast validation shared by all punishment records.
*/
static void validate(Instant createdAt, Instant expiresAt, Revocation revocation) {
if (expiresAt != null && expiresAt.isBefore(createdAt)) {
throw new IllegalArgumentException("expiresAt cannot be before createdAt");
}

if (revocation != null && revocation.revokedAt().isBefore(createdAt)) {
throw new IllegalArgumentException("revokedAt cannot be before createdAt");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.eternalcode.core.feature.punishment;

import java.time.Instant;

public enum PunishmentStatus {

ACTIVE,
EXPIRED,
REVOKED,
INSTANT;

public static PunishmentStatus resolve(Instant expiresAt, Instant revokedAt, Instant now) {

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.

ignorujesz fakt, że revokedAt może być później niż przekazany now. Przykład: kara cofnięta 10 stycznia, a pytamy o status 5 stycznia -> obecnie dostaniemy REVOKED, mimo że wtedy kara była jeszcze aktywna.

boolean revokedBeforeExpiry = revokedAt != null && (expiresAt == null || revokedAt.isBefore(expiresAt));

if (revokedBeforeExpiry) {
return REVOKED;
}

boolean expired = expiresAt != null && !now.isBefore(expiresAt);

if (expired) {
return EXPIRED;
}

return ACTIVE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.eternalcode.core.feature.punishment;

import java.util.UUID;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

public record PunishmentTarget(UUID uuid, String name) {

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.

w kilku miejscach uzywasz PunishmentTarget jednoczesnie jako targetu i operatora. Nazwa jest więc myląca.

PunishmentTarget should not be used for both the punished player and the operator.

Right now the API models console as a fake player using CONSOLE_UUID, which is a sign that these are two different domain concepts being forced into one type.

Please split this into something like:

public record PunishmentTarget(UUID uuid, String name) {
}

public sealed interface PunishmentActor {

    record Player(UUID uuid, String name) implements PunishmentActor {
    }

    record Console(String name) implements PunishmentActor {
    }
}

Then punishments should look like:

PunishmentTarget target();
PunishmentActor operator();

and Revocation should also use PunishmentActor for revokedBy.

This removes the fake console UUID, prevents invalid cases like using console as a punishment target, and makes the API reflect the actual domain model instead of overloading one type with two unrelated responsibilities.


public static final UUID CONSOLE_UUID = UUID.nameUUIDFromBytes("CONSOLE_UUID".getBytes());

public static PunishmentTarget of(OfflinePlayer player) {
String name = player.getName();

if (name == null) {
throw new IllegalArgumentException("Player " + player.getUniqueId() + " has no known name - resolve the player before punishing");
}

return new PunishmentTarget(player.getUniqueId(), name);
}

public static PunishmentTarget of(Player player) {

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.

Bukkit sam w sobie nie powinien przeciekac do modelu api, tak sadze. proponuje zebys tutaj robil to wszystko na uuid i w srodku cora tylko mapowal.

return new PunishmentTarget(player.getUniqueId(), player.getName());
}

public static PunishmentTarget of(CommandSender sender) {
if (sender instanceof Player player) {
return of(player);
}

return new PunishmentTarget(CONSOLE_UUID, sender.getName());
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}

return obj instanceof PunishmentTarget other && this.uuid.equals(other.uuid);
}

@Override
public int hashCode() {
return this.uuid.hashCode();
}
}
Comment on lines +34 to +47

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.

w jakim celu nadpisujesz te metody?

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.eternalcode.core.feature.punishment;

import java.time.Instant;

/**
* Describes who and when revoked a punishment (unban, unmute, ...).
*/
public record Revocation(PunishmentTarget revokedBy, Instant revokedAt) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.eternalcode.core.feature.punishment.ban;

import com.eternalcode.core.feature.punishment.Punishment;
import com.eternalcode.core.feature.punishment.PunishmentTarget;
import com.eternalcode.core.feature.punishment.Revocation;

import java.time.Instant;
import java.util.UUID;

/**
* Immutable ban of a single player account.
*
* @param expiresAt null = permanent
* @param revocation null = not revoked
*/
public record Ban(
UUID id,
PunishmentTarget target,
PunishmentTarget operator,
String reason,
Instant createdAt,
Instant expiresAt,
Revocation revocation
) implements Punishment {

public Ban {
Punishment.validate(createdAt, expiresAt, revocation);
}

public static Ban issue(PunishmentTarget target, PunishmentTarget operator, String reason, Instant expiresAt) {

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.

Public API consumers should not be able to create punishment objects that look persisted but were never processed by EternalCore. Creation should go through the service.

return new Ban(UUID.randomUUID(), target, operator, reason, Instant.now(), expiresAt, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.eternalcode.core.feature.punishment.ban;

import com.eternalcode.core.feature.punishment.PunishmentTarget;

import net.kyori.adventure.text.Component;

import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

public interface BanService {

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.

Api do banów nie zadziała, ponieważ nic nie przekazujesz w EternalCoreApi interfejsie. nikt nie bedzie mial do zadnego serwisu dostepu.


/**
* Blocking, must not be called from the main thread.
*/
Ban ban(PunishmentTarget target, PunishmentTarget operator, String reason, Instant expiresAt, List<Component> kickMessage);

/**
* Blocking, must not be called from the main thread.
*/
void unban(PunishmentTarget target, PunishmentTarget operator);

boolean isBanned(UUID targetUuid);

Optional<Ban> getActiveBan(UUID targetUuid);

List<Ban> activeBans();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.eternalcode.core.feature.punishment.banip;

import com.eternalcode.core.feature.punishment.Punishment;
import com.eternalcode.core.feature.punishment.PunishmentTarget;
import com.eternalcode.core.feature.punishment.Revocation;

import java.time.Instant;
import java.util.UUID;

/**
* Immutable ban of an IP address. {@code target} is the player the ban was issued for.
*
* @param expiresAt null = permanent
* @param revocation null = not revoked
*/
public record BanIp(
UUID id,
String ip,
PunishmentTarget target,
PunishmentTarget operator,
String reason,
Instant createdAt,
Instant expiresAt,
Revocation revocation
) implements Punishment {

public BanIp {
Punishment.validate(createdAt, expiresAt, revocation);
}

public static BanIp issue(String ip, PunishmentTarget target, PunishmentTarget operator, String reason, Instant expiresAt) {
return new BanIp(UUID.randomUUID(), ip, target, operator, reason, Instant.now(), expiresAt, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.eternalcode.core.feature.punishment.banip;

import com.eternalcode.core.feature.punishment.PunishmentTarget;

import net.kyori.adventure.text.Component;

import java.time.Instant;
import java.util.List;
import java.util.Optional;

public interface BanIpService {

/**
* Blocking, must not be called from the main thread.
*/
BanIp banIp(String ip, PunishmentTarget target, PunishmentTarget operator, String reason, Instant expiresAt, List<Component> kickMessage);

/**
* Blocking, must not be called from the main thread.
*/
void unbanIp(String ip, PunishmentTarget operator);

boolean isIpBanned(String ip);

Optional<BanIp> getActiveIpBan(String ip);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.eternalcode.core.feature.punishment.kick;

import com.eternalcode.core.feature.punishment.Punishment;
import com.eternalcode.core.feature.punishment.PunishmentStatus;
import com.eternalcode.core.feature.punishment.PunishmentTarget;
import com.eternalcode.core.feature.punishment.Revocation;

import java.time.Instant;
import java.util.UUID;

/**
* Immutable kick. Kick is instantaneous: it never expires and cannot be revoked,
* its status is always {@link PunishmentStatus#INSTANT}.
*
* @param massKick true when the kick was part of /kickall
*/
public record Kick(
UUID id,
PunishmentTarget target,
PunishmentTarget operator,
String reason,
Instant createdAt,
boolean massKick
) implements Punishment {

public static Kick issue(PunishmentTarget target, PunishmentTarget operator, String reason, boolean massKick) {
return new Kick(UUID.randomUUID(), target, operator, reason, Instant.now(), massKick);
}

@Override
public Instant expiresAt() {
return null;

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.

nie podoba mi sie zwracanie takie nulla tutaj, to oznacza ze wszystkie kicki sa pernamentne tak? a status jest instant.

propozycja::

public interface Punishment {
    UUID id();
    PunishmentTarget target();
    PunishmentActor operator();
    String reason();
    Instant createdAt();
}

public interface ExpirablePunishment extends Punishment {
    Instant expiresAt();
    Revocation revocation();
}

}

@Override
public Revocation revocation() {
return null;
}

@Override
public PunishmentStatus status(Instant now) {
return PunishmentStatus.INSTANT;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.eternalcode.core.feature.punishment.kick;

import com.eternalcode.core.feature.punishment.PunishmentTarget;

import net.kyori.adventure.text.Component;

import java.util.List;

public interface KickService {

/**
* Blocking, must not be called from the main thread.
*/
Kick kick(PunishmentTarget target, PunishmentTarget operator, String reason, List<Component> kickMessage, boolean massKick);
}
Loading