Skip to content

Commit

Permalink
Implement punishment logs
Browse files Browse the repository at this point in the history
  • Loading branch information
JvstvsHD committed Oct 25, 2024
1 parent 1c59fab commit 5c4bd8e
Show file tree
Hide file tree
Showing 13 changed files with 228 additions and 34 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
name: Build project and generate JavaDocs
on:
pull_request:
branches:
- "**"
types: [opened, reopened, edited]
jobs:
publish:
runs-on: ubuntu-latest
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@

name: Publish package to the Maven Central Repository
on:
release:
types: [created]
push:
branches:
- '**'


jobs:
publish:
runs-on: ubuntu-latest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

package de.jvstvshd.necrify.api.duration;

import de.jvstvshd.necrify.api.punishment.Punishment;
import de.jvstvshd.necrify.api.punishment.TemporalPunishment;
import org.jetbrains.annotations.ApiStatus;

import java.sql.Timestamp;
Expand Down Expand Up @@ -117,6 +119,20 @@ static PunishmentDuration fromDuration(Duration duration) {
return rpd;
}

/**
* Converts the given {@link Punishment} into a {@link PunishmentDuration}. If the punishment is a {@link TemporalPunishment},
* the duration of the punishment will be returned. Otherwise, the duration will be permanent.
* @param punishment the punishment to convert
* @return the duration of the punishment or a permanent duration if this is a non-temporal punishment
*/
static PunishmentDuration ofPunishment(Punishment punishment) {
if (punishment instanceof TemporalPunishment temporalPunishment) {
return temporalPunishment.getDuration();
} else {
return PermanentPunishmentDuration.PERMANENT;
}
}

/**
* Whether this duration is permanent meaning the expiration should be 31.12.9999, 23:59:59.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package de.jvstvshd.necrify.api.punishment;

import de.jvstvshd.necrify.api.PunishmentException;
import de.jvstvshd.necrify.api.punishment.log.PunishmentLog;
import de.jvstvshd.necrify.api.punishment.util.ReasonHolder;
import de.jvstvshd.necrify.api.user.NecrifyUser;
import net.kyori.adventure.text.Component;
Expand Down Expand Up @@ -186,4 +187,12 @@ default Punishment getSuccessorOrNull() {
*/
@Nullable
Punishment getPredecessor();

/**
* Loads the punishment log of this punishment. This will load all log entries that have been made on this punishment.
* @return a {@link CompletableFuture} containing the punishment log of this punishment
* @since 1.2.2
*/
@NotNull
CompletableFuture<PunishmentLog> loadPunishmentLog();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package de.jvstvshd.necrify.api.punishment.log;

import de.jvstvshd.necrify.api.punishment.Punishment;
import de.jvstvshd.necrify.api.user.NecrifyUser;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -84,5 +85,5 @@ default PunishmentLogEntry getLatestEntry() {
* @param action the action to log
* @param message the message to log
*/
void log(@NotNull PunishmentLogAction action, @NotNull String message);
void log(@NotNull PunishmentLogAction action, @NotNull String message, @NotNull NecrifyUser actor);
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ public interface PunishmentLogAction {
*/
PunishmentLogAction REMOVED = new SimplePunishmentLogAction("removed", true);

/**
* An unknown action was performed. This action is returned as default if the stored action type cannot be resolved to
* a proper type. This action can be logged multiple times.
*/
PunishmentLogAction UNKNOWN = new SimplePunishmentLogAction("unknown", false);

/**
* A simple implementation of {@link PunishmentLogAction}. This class only contains the name and whether the action can only be logged once or more.
* @param name the name of the action
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,38 @@
import de.jvstvshd.necrify.api.user.NecrifyUser;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.time.Instant;
import java.util.Objects;

/**
* Represents an entry in a {@link PunishmentLog}. This contains all information about a punishment log entry.
* There is no information about old values if they have been changed, this has to be done by using {@link #previous() the previous entry}.
* @param actor the actor who performed the action
* @param message the message of the action
* @param duration the duration of the punishment
* @param reason the reason of the punishment
*
* @param actor the actor who performed the action or null if the user does not exist anymore
* @param message the message of the action
* @param duration the duration of the punishment
* @param reason the reason of the punishment
* @param predecessor the predecessor of the punishment or null if there is none
* @param punishment the punishment this entry belongs to
* @param successor the successor of the punishment or null if there is none
* @param action the action that was performed
* @param log the log this entry belongs to
* @param instant the instant the action was performed
* @param index the index of this entry in the log (0-based)
* @param punishment the punishment this entry belongs to
* @param successor the successor of the punishment or null if there is none
* @param action the action that was performed
* @param log the log this entry belongs to
* @param instant the instant the action was performed
* @param index the index of this entry in the log (0-based)
* @since 1.2.2
*/
public record PunishmentLogEntry(NecrifyUser actor, String message, PunishmentDuration duration, Component reason,
Punishment predecessor, Punishment punishment, Punishment successor,
PunishmentLogAction action, PunishmentLog log, Instant instant, int index) {
public record PunishmentLogEntry(@Nullable NecrifyUser actor, @Nullable String message,
@NotNull PunishmentDuration duration, @NotNull Component reason,
@Nullable Punishment predecessor, @NotNull Punishment punishment,
@Nullable Punishment successor,
@NotNull PunishmentLogAction action, @NotNull PunishmentLog log,
@NotNull Instant instant, int index) implements Comparable<PunishmentLogEntry> {

/**
* Returns the previous entry in the log. If this is the first entry, this entry is returned.
*
* @return the previous entry in the log
*/
@NotNull
Expand All @@ -57,6 +64,7 @@ public PunishmentLogEntry previous() {

/**
* Returns the next entry in the log. If this is the last entry, this entry is returned.
*
* @return the next entry in the log
*/
@NotNull
Expand All @@ -67,10 +75,16 @@ public PunishmentLogEntry next() {
/**
* Returns the affected user of the punishment. This is the user who is affected by the punishment and equivalent
* to {@link #punishment()}.{@link Punishment#getUser() getUser()}.
*
* @return the affected user of the punishment
*/
@NotNull
public NecrifyUser getAffectedUser() {
return punishment.getUser();
}

@Override
public int compareTo(@NotNull PunishmentLogEntry o) {
return Integer.compare(index, o.index);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,4 @@ default Optional<Punishment> getPunishment(@NotNull UUID punishmentUuid) {
*/
@NotNull
Locale getLocale();

/**
* Loads the punishment log of this user. The punishment log contains all actions that were performed on this user.
* This method may take some time to complete. The returned future will complete exceptionally with {@link java.util.NoSuchElementException}
* if the user does not exist in the system.
* @return a {@link CompletableFuture} containing the punishment log of this user.
*/
@NotNull
CompletableFuture<PunishmentLog> loadPunishmentLog();
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
import de.jvstvshd.necrify.api.event.punishment.PunishmentPersecutedEvent;
import de.jvstvshd.necrify.api.message.MessageProvider;
import de.jvstvshd.necrify.api.punishment.Punishment;
import de.jvstvshd.necrify.api.punishment.log.PunishmentLog;
import de.jvstvshd.necrify.api.user.NecrifyUser;
import de.jvstvshd.necrify.common.AbstractNecrifyPlugin;
import de.jvstvshd.necrify.common.punishment.log.NecrifyPunishmentLog;
import de.jvstvshd.necrify.common.util.Util;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.intellij.lang.annotations.Language;
Expand All @@ -51,6 +54,7 @@ public abstract class AbstractPunishment implements Punishment {
private final AbstractNecrifyPlugin plugin;
private LocalDateTime creationTime;
private Punishment successor;
private PunishmentLog cachedLog;

@Language("sql")
protected final static String APPLY_PUNISHMENT = "INSERT INTO necrify_punishment" +
Expand Down Expand Up @@ -237,4 +241,16 @@ public boolean hasBeenCreated() {
void setSuccessor0(Punishment successor) {
this.successor = successor;
}

@Override
public @NotNull CompletableFuture<PunishmentLog> loadPunishmentLog() {
if (cachedLog != null) {
return CompletableFuture.completedFuture(cachedLog);
}
return Util.executeAsync(() -> {
var log = new NecrifyPunishmentLog(this, plugin);
log.load(false);
return (cachedLog = log);
}, executor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ protected CompletableFuture<Punishment> applyPunishment() throws PunishmentExcep
var total = temporalSuccessor.totalDuration();
successorNewExpiration = duration.expiration().plus(total.javaDuration());
} else {
successorNewExpiration = PunishmentDuration.permanent().expiration();
successorNewExpiration = PunishmentDuration.PERMANENT.expiration();
}
var issuanceSuccessor = duration.expiration();
Query.query(APPLY_TIMESTAMP_UPDATE)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* This file is part of Necrify (formerly Velocity Punishment), a plugin designed to manage player's punishments for the platforms Velocity and partly Paper.
* Copyright (C) 2022-2024 JvstvsHD
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package de.jvstvshd.necrify.common.punishment.log;

import de.chojo.sadu.queries.api.call.Call;
import de.chojo.sadu.queries.api.query.Query;
import de.jvstvshd.necrify.api.duration.PunishmentDuration;
import de.jvstvshd.necrify.api.punishment.Punishment;
import de.jvstvshd.necrify.api.punishment.log.PunishmentLog;
import de.jvstvshd.necrify.api.punishment.log.PunishmentLogAction;
import de.jvstvshd.necrify.api.punishment.log.PunishmentLogActionRegistry;
import de.jvstvshd.necrify.api.punishment.log.PunishmentLogEntry;
import de.jvstvshd.necrify.api.user.NecrifyUser;
import de.jvstvshd.necrify.api.user.UserManager;
import de.jvstvshd.necrify.common.AbstractNecrifyPlugin;
import de.jvstvshd.necrify.common.io.Adapters;
import de.jvstvshd.necrify.common.util.Util;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.sql.Timestamp;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

public class NecrifyPunishmentLog implements PunishmentLog {

private final Punishment punishment;
private final List<PunishmentLogEntry> entries = Collections.synchronizedList(new ArrayList<>());
private final AbstractNecrifyPlugin plugin;
private final UserManager userManager;

public NecrifyPunishmentLog(Punishment punishment, AbstractNecrifyPlugin plugin) {
this.punishment = punishment;
this.plugin = plugin;
this.userManager = plugin.getUserManager();
}

/**
* Loads the log from the database. This method should be called right after instantiation of this object, otherwise
* there will be no data contained in this object.
* <p>This method is ran synchronously and should not be called on the main thread. Use an asynchronous context yourself.
*
* @param loadIfAlreadyLoaded whether to load the log if it is already loaded
*/
public synchronized void load(boolean loadIfAlreadyLoaded) {
if (!loadIfAlreadyLoaded && !entries.isEmpty()) {
return;
}
var entries = Query.query("SELECT * FROM punishment_log WHERE punishment_id = ? ORDER BY id ASC")
.single(Call.of().bind(punishment.getUuid(), Adapters.UUID_ADAPTER))
.map(row -> {
var id = row.getInt(1);
var actorUuid = row.getObject(2, UUID.class);
NecrifyUser actor;
if (actorUuid == null) {
actor = null;
} else {
actor = userManager.loadUser(actorUuid).join().orElseThrow(() -> new IllegalStateException("Actor not found."));
}
var message = row.getString(3);
var duration = PunishmentDuration.fromTimestamp(row.getTimestamp(4));
var reason = MiniMessage.miniMessage().deserialize(row.getString(5));
var predecessor = plugin.getPunishment(row.getObject(6, UUID.class)).join().orElse(null);
var successor = plugin.getPunishment(row.getObject(7, UUID.class)).join().orElse(null);
var action = PunishmentLogActionRegistry.getAction(row.getString(8)).orElse(PunishmentLogAction.UNKNOWN);
var instant = row.getTimestamp(9).toInstant();
return new PunishmentLogEntry(actor, message, duration, reason, predecessor, punishment, successor, action, this, instant, id);
}).all();
Collections.sort(entries);
this.entries.clear();
this.entries.addAll(entries);
}

@Override
public @NotNull Punishment getPunishment() {
return punishment;
}

@Override
public @NotNull List<PunishmentLogEntry> getEntries() {
return entries;
}

@Override
public @NotNull List<PunishmentLogEntry> getEntries(@NotNull PunishmentLogAction action) {
return entries.stream().filter(entry -> entry.action().equals(action)).toList();
}

@Override
public @Nullable PunishmentLogEntry getEntry(@NotNull PunishmentLogAction action) {
return getEntries(action).getFirst();
}

@Override
public @NotNull PunishmentLogEntry getEntry(int index) {
return entries.get(index);
}

@Override
public void log(@NotNull PunishmentLogAction action, @NotNull String message, @NotNull NecrifyUser actor) {
var entry = new PunishmentLogEntry(actor, message, PunishmentDuration.ofPunishment(punishment), punishment.getReason(), punishment.getPredecessor(), punishment, punishment.getSuccessor(), action, this, Instant.now(), entries.size());
//insert at correct position (linked list)
entries.add(entry);
Util.executeAsync(() -> Query.query("INSERT INTO punishment_log (punishment_id, player_id, message, expiration, reason, predecessor, successor, action, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
.single(Call.of().bind(punishment.getUuid(), Adapters.UUID_ADAPTER)
.bind(actor.getUuid(), Adapters.UUID_ADAPTER)
.bind(message)
.bind(PunishmentDuration.ofPunishment(punishment).expirationAsTimestamp())
.bind(MiniMessage.miniMessage().serialize(punishment.getReason()))
.bind(punishment.getPredecessor() == null ? null : punishment.getPredecessor().getUuid(), Adapters.UUID_ADAPTER)
.bind(punishment.getSuccessorOrNull() == null ? null : punishment.getSuccessor().getUuid(), Adapters.UUID_ADAPTER)
.bind(action.name())
.bind(Timestamp.from(Instant.now())))
.insert().changed(), plugin.getExecutor());

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS punishment_log (
id SERIAL PRIMARY KEY,
punishment_id UUID,
player_id UUID REFERENCES necrify_users (uuid) ON DELETE SET NULL,
message TEXT,
expiration TIMESTAMP,
reason TEXT,
predecessor UUID DEFAULT NULL,
successor UUID DEFAULT NULL,
action VARCHAR(128),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
Loading

0 comments on commit 5c4bd8e

Please sign in to comment.