Sketch of connection refusing system
This commit is contained in:
parent
49e5051aaf
commit
053bc0a83b
12 changed files with 663 additions and 93 deletions
61
src/main/java/net/kuraczyk/banhammer/BanHammer.java
Normal file
61
src/main/java/net/kuraczyk/banhammer/BanHammer.java
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
package net.kuraczyk.banhammer;
|
||||
|
||||
import net.kuraczyk.banhammer.events.PlayerPreLogin;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import net.kuraczyk.banhammer.utils.*;
|
||||
|
||||
public final class BanHammer extends JavaPlugin {
|
||||
|
||||
public FileConfiguration config;
|
||||
public DBManager db;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
saveDefaultConfig();
|
||||
config = getConfig();
|
||||
|
||||
try {
|
||||
Class.forName("net.kuraczyk.libs.postgresql.Driver");
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
db = new DBManager(
|
||||
config.getString("database.host"),
|
||||
config.getInt("database.port"),
|
||||
config.getString("database.user"),
|
||||
config.getString("database.pass"),
|
||||
config.getString("database.database"),
|
||||
this.getClassLoader()
|
||||
);
|
||||
String error = db.connect();
|
||||
if(error != null){
|
||||
getLogger().severe(
|
||||
"Failed to connect to DB!!!!\n"
|
||||
+error
|
||||
);
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
}
|
||||
|
||||
getLogger().info(ChatColor.translateAlternateColorCodes('&', "&a&b+&r&8 | &aBan&7Hammer"));
|
||||
|
||||
getServer().getPluginManager().registerEvents(new PlayerPreLogin(this), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if(db != null){
|
||||
String error = db.disconnect();
|
||||
if(error != null) {
|
||||
getLogger().severe(
|
||||
"Error occured while disconnecting from from DB!!!!\n"
|
||||
+error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getLogger().info(ChatColor.translateAlternateColorCodes('&', "&c&b-&r&8 | &aBan&7Hammer"));
|
||||
}
|
||||
}
|
||||
126
src/main/java/net/kuraczyk/banhammer/events/PlayerPreLogin.java
Normal file
126
src/main/java/net/kuraczyk/banhammer/events/PlayerPreLogin.java
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package net.kuraczyk.banhammer.events;
|
||||
|
||||
import net.kuraczyk.banhammer.BanHammer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.sql.*;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import net.kuraczyk.banhammer.utils.TextUtils;
|
||||
|
||||
public class PlayerPreLogin implements Listener {
|
||||
|
||||
BanHammer plugin;
|
||||
|
||||
Component kickMessage = Component.text("If this message is displayed something went terribly wrong...\nModule: BanHammer");
|
||||
|
||||
public PlayerPreLogin(BanHammer m) {
|
||||
plugin = m;
|
||||
m.getClass();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerPreLogin(AsyncPlayerPreLoginEvent e) {
|
||||
if(checkPlayerBanStatus(e.getPlayerProfile().getName(), e.getUniqueId(), e.getAddress()))
|
||||
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_BANNED, kickMessage);
|
||||
}
|
||||
|
||||
private boolean checkPlayerBanStatus(String nickname, UUID uuid, InetAddress ip) {
|
||||
try {
|
||||
Connection conn = plugin.db.getConnection();
|
||||
if(conn!=null){
|
||||
PreparedStatement ps = conn.prepareStatement("SELECT * from player_bans WHERE (player_name = ? OR player_uuid = ?::uuid) AND (scope = ? OR scope = '*')");
|
||||
ps.setString(1, nickname);
|
||||
ps.setObject(2, uuid);
|
||||
ps.setString(3, plugin.getConfig().getString("serverID"));
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while(rs.next()){
|
||||
|
||||
String reason = rs.getString("reason");
|
||||
String parsedDate;
|
||||
|
||||
if(rs.getObject("end_time")!=null){
|
||||
long timeLeft = ((Timestamp)rs.getObject("end_time")).getTime() - new Timestamp(System.currentTimeMillis()).getTime();
|
||||
|
||||
if(timeLeft <= 0) {
|
||||
PreparedStatement archiveBan = conn.prepareStatement("DELETE FROM player_bans WHERE id = ?::uuid");
|
||||
archiveBan.setString(1, rs.getObject("id").toString());
|
||||
archiveBan.executeUpdate();
|
||||
continue;
|
||||
}
|
||||
|
||||
long timeLeftDays = TimeUnit.MILLISECONDS.toDays(timeLeft);
|
||||
timeLeft-=TimeUnit.DAYS.toMillis(timeLeftDays);
|
||||
long timeLeftHours = TimeUnit.MILLISECONDS.toHours(timeLeft);
|
||||
timeLeft-=TimeUnit.HOURS.toMillis(timeLeftHours);
|
||||
long timeLeftMinutes = TimeUnit.MILLISECONDS.toMinutes(timeLeft);
|
||||
timeLeft-=TimeUnit.MINUTES.toMillis(timeLeftMinutes);
|
||||
long timeLeftSeconds = TimeUnit.MILLISECONDS.toSeconds(timeLeft);
|
||||
timeLeft-=TimeUnit.SECONDS.toMillis(timeLeftSeconds);
|
||||
|
||||
parsedDate = TextUtils.parsePolishDate(timeLeftDays, timeLeftHours, timeLeftMinutes, timeLeftSeconds);
|
||||
kickMessage = TextUtils.colorString(TextUtils.resolvePlaceholders(String.format("Zostałeś zbanowany na %s.\nPowód: %s", parsedDate, reason)));
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
kickMessage = TextUtils.colorString(TextUtils.resolvePlaceholders(String.format("Zostałeś permanentnie zbanowany.\nPowód: %s", reason)));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
PreparedStatement ps2 = conn.prepareStatement("SELECT * from ip_bans WHERE (ip_address = ?::inet) AND (scope = ? OR scope = '*')");
|
||||
ps2.setObject(1, ip.getHostAddress());
|
||||
ps2.setString(2, plugin.getConfig().getString("serverID"));
|
||||
ResultSet rs2 = ps2.executeQuery();
|
||||
while(rs2.next()) {
|
||||
String reason = rs2.getString("reason");
|
||||
String parsedDate;
|
||||
|
||||
if(rs2.getObject("end_time")!=null){
|
||||
long timeLeft = ((Timestamp)rs2.getObject("end_time")).getTime() - new Timestamp(System.currentTimeMillis()).getTime();
|
||||
|
||||
if(timeLeft <= 0) {
|
||||
PreparedStatement archiveBan = conn.prepareStatement("DELETE FROM ip_bans WHERE id = ?::uuid");
|
||||
archiveBan.setString(1, rs2.getObject("id").toString());
|
||||
archiveBan.executeUpdate();
|
||||
continue;
|
||||
}
|
||||
|
||||
long timeLeftDays = TimeUnit.MILLISECONDS.toDays(timeLeft);
|
||||
timeLeft-=TimeUnit.DAYS.toMillis(timeLeftDays);
|
||||
long timeLeftHours = TimeUnit.MILLISECONDS.toHours(timeLeft);
|
||||
timeLeft-=TimeUnit.HOURS.toMillis(timeLeftHours);
|
||||
long timeLeftMinutes = TimeUnit.MILLISECONDS.toMinutes(timeLeft);
|
||||
timeLeft-=TimeUnit.MINUTES.toMillis(timeLeftMinutes);
|
||||
long timeLeftSeconds = TimeUnit.MILLISECONDS.toSeconds(timeLeft);
|
||||
timeLeft-=TimeUnit.SECONDS.toMillis(timeLeftSeconds);
|
||||
|
||||
parsedDate = TextUtils.parsePolishDate(timeLeftDays, timeLeftHours, timeLeftMinutes, timeLeftSeconds);
|
||||
kickMessage = TextUtils.colorString(TextUtils.resolvePlaceholders(String.format("Zostałeś IPzbanowany na %s.\nPowód: %s", parsedDate, reason)));
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
kickMessage = TextUtils.colorString(TextUtils.resolvePlaceholders(String.format("Zostałeś permanentnie IPzbanowany.\nPowód: %s", reason)));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().severe(e.toString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
92
src/main/java/net/kuraczyk/banhammer/utils/DBManager.java
Normal file
92
src/main/java/net/kuraczyk/banhammer/utils/DBManager.java
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package net.kuraczyk.banhammer.utils;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.flywaydb.core.Flyway;
|
||||
import org.flywaydb.core.api.output.MigrateResult;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Struct;
|
||||
|
||||
public class DBManager {
|
||||
|
||||
private String host, user, pass, database;
|
||||
private int port;
|
||||
private Connection connection;
|
||||
private ClassLoader classLoader;
|
||||
|
||||
|
||||
public DBManager(String host, int port, String user, String pass, String database, ClassLoader classLoader) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.user = user;
|
||||
this.pass = pass;
|
||||
this.database = database;
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
public String connect() {
|
||||
try {
|
||||
if(connection != null && !connection.isClosed()){
|
||||
return null;
|
||||
}
|
||||
|
||||
connection = DriverManager.getConnection(String.format("jdbc:postgresql://%s:%d/%s", host, port, database), user, pass);
|
||||
|
||||
String migrationSuccess = performMigration();
|
||||
if(migrationSuccess != null){
|
||||
return migrationSuccess;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
} catch (SQLException e) {
|
||||
return e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public String disconnect() {
|
||||
try {
|
||||
if(connection != null && !connection.isClosed()){
|
||||
connection.close();
|
||||
return null;
|
||||
}
|
||||
return "Error";
|
||||
} catch (SQLException e) {
|
||||
return e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public Connection getConnection() throws SQLException {
|
||||
if(connection == null || connection.isClosed()){
|
||||
return null;
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
private String performMigration() {
|
||||
try {
|
||||
Flyway flyway = Flyway.configure(classLoader)
|
||||
.driver("net.kuraczyk.libs.postgresql.Driver")
|
||||
.dataSource(String.format("jdbc:postgresql://%s:%d/%s", host, port, database), user, pass)
|
||||
.locations("classpath:db/migration")
|
||||
.baselineOnMigrate(true)
|
||||
.validateOnMigrate(true)
|
||||
.cleanDisabled(true)
|
||||
.table("flyway_schema_history")
|
||||
.load();
|
||||
|
||||
MigrateResult result = flyway.migrate();
|
||||
if (result.success) {
|
||||
return null;
|
||||
} else {
|
||||
return result.warnings.toString();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
return e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
121
src/main/java/net/kuraczyk/banhammer/utils/TextUtils.java
Normal file
121
src/main/java/net/kuraczyk/banhammer/utils/TextUtils.java
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
package net.kuraczyk.banhammer.utils;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class TextUtils {
|
||||
|
||||
public static Component colorString(String s){
|
||||
return colorMarkupFormattedString(
|
||||
colorLegacyFormattedString(s)
|
||||
);
|
||||
}
|
||||
|
||||
private static Component colorMarkupFormattedString(String s){
|
||||
return MiniMessage.miniMessage().deserialize(s);
|
||||
}
|
||||
|
||||
private static String colorLegacyFormattedString(String s){
|
||||
return ChatColor.translateAlternateColorCodes('&', s);
|
||||
}
|
||||
|
||||
public static String resolvePlaceholders(String s){
|
||||
return s.replace("{onlinePlayerCount}", String.valueOf(
|
||||
Bukkit.getOnlinePlayers().size()+Arrays.stream(Bukkit.getOfflinePlayers()).count()
|
||||
));
|
||||
}
|
||||
public static String resolvePlaceholders(String s, Player p){
|
||||
return resolvePlaceholders(s)
|
||||
.replace("{playerName}", p.getName())
|
||||
.replace("{ping}", String.valueOf(p.getPing()));
|
||||
}
|
||||
|
||||
public static String parsePolishDate(long days, long hours, long minutes, long seconds){
|
||||
String parsedDays, parsedHours, parsedMinutes, parsedSeconds;
|
||||
|
||||
// Parse days string block
|
||||
if(days >= 2){
|
||||
parsedDays="dni";
|
||||
}
|
||||
else if (days==1){
|
||||
parsedDays="dzień";
|
||||
}
|
||||
else{
|
||||
parsedDays="";
|
||||
}
|
||||
if(parsedDays!="") parsedDays=Long.toString(days)+' '+parsedDays+", ";
|
||||
|
||||
// Parse hours string block
|
||||
if(hours >= 20 || (hours < 10 && hours > 1)){
|
||||
if(hours%10 >=2 && hours%10 <=4){
|
||||
parsedHours="godziny";
|
||||
}
|
||||
else{
|
||||
parsedHours="godzin";
|
||||
}
|
||||
}
|
||||
else if(hours < 20 && hours >= 10){
|
||||
parsedHours="godzin";
|
||||
}
|
||||
else if (hours==1){
|
||||
parsedHours="godzina";
|
||||
}
|
||||
else{
|
||||
parsedHours="";
|
||||
}
|
||||
if(parsedHours!="") parsedHours=Long.toString(hours)+' '+parsedHours+", ";
|
||||
|
||||
// Parse minutes string block
|
||||
if(minutes >= 20 || (minutes < 10 && minutes > 1)){
|
||||
if(minutes%10 >=2 && minutes%10 <=4){
|
||||
parsedMinutes="minuty";
|
||||
}
|
||||
else{
|
||||
parsedMinutes="minut";
|
||||
}
|
||||
}
|
||||
else if(minutes < 20 && minutes >= 10){
|
||||
parsedMinutes="minut";
|
||||
}
|
||||
else if (minutes==1){
|
||||
parsedMinutes="minuta";
|
||||
}
|
||||
else{
|
||||
parsedMinutes="";
|
||||
}
|
||||
if(parsedMinutes!="") parsedMinutes=Long.toString(minutes)+' '+parsedMinutes+", ";
|
||||
|
||||
// Parse seconds string block
|
||||
if(seconds >= 20 || (seconds < 10 && seconds > 1)){
|
||||
if(seconds%10 >=2 && seconds%10 <=4){
|
||||
parsedSeconds="sekundy";
|
||||
}
|
||||
else{
|
||||
parsedSeconds="sekund";
|
||||
}
|
||||
}
|
||||
else if(seconds < 20 && seconds >= 10){
|
||||
parsedSeconds="sekund";
|
||||
}
|
||||
else if (seconds==1){
|
||||
parsedSeconds="sekunda";
|
||||
}
|
||||
else{
|
||||
parsedSeconds="";
|
||||
}
|
||||
if(parsedSeconds!="") parsedSeconds=Long.toString(seconds)+' '+parsedSeconds+", ";
|
||||
|
||||
// Create final gramatically correct string
|
||||
String result = String.format("%s%s%s%s", parsedDays, parsedHours, parsedMinutes, parsedSeconds);
|
||||
if(result.length()>=2) result = result.substring(0, result.length() - 2);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
8
src/main/resources/config.yml
Normal file
8
src/main/resources/config.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
database:
|
||||
host: "localhost"
|
||||
port: 5432
|
||||
user: "postgres"
|
||||
pass: "postgres"
|
||||
database: "postgres"
|
||||
|
||||
serverID: "mcserver"
|
||||
129
src/main/resources/db/migration/V1__Initial_schema.sql
Normal file
129
src/main/resources/db/migration/V1__Initial_schema.sql
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
CREATE TABLE player_bans (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
player_name VARCHAR(16) NOT NULL,
|
||||
player_uuid UUID NOT NULL,
|
||||
reason TEXT,
|
||||
scope VARCHAR(20) NOT NULL DEFAULT '*',
|
||||
operator_name VARCHAR(16) NOT NULL,
|
||||
operator_uuid UUID,
|
||||
start_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
end_time TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE ip_bans (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
ip_address INET NOT NULL,
|
||||
reason TEXT,
|
||||
scope VARCHAR(20) NOT NULL DEFAULT '*',
|
||||
operator_name VARCHAR(16) NOT NULL,
|
||||
operator_uuid UUID,
|
||||
start_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
end_time TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE ban_history (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
target JSONB NOT NULL,
|
||||
reason TEXT,
|
||||
scope VARCHAR(20) NOT NULL DEFAULT '*',
|
||||
operator_name VARCHAR(16) NOT NULL,
|
||||
operator_uuid UUID,
|
||||
start_time TIMESTAMP NOT NULL,
|
||||
end_time TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE appeals (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
ban_id UUID NOT NULL,
|
||||
target JSONB NOT NULL, --so appealing player could use email, account, discord
|
||||
sustained BOOLEAN, -- null is pending
|
||||
response TEXT,
|
||||
operator_name VARCHAR(16),
|
||||
operator_uuid UUID,
|
||||
appeal_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
resolve_date TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_player_bans_uuid ON player_bans(player_uuid);
|
||||
CREATE INDEX idx_player_bans_name ON player_bans(player_name);
|
||||
CREATE INDEX idx_player_bans_scope ON player_bans(scope);
|
||||
CREATE INDEX idx_player_bans_end_time ON player_bans(end_time) WHERE end_time IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_player_bans_active_lookup
|
||||
ON player_bans(player_uuid, scope);
|
||||
|
||||
CREATE INDEX idx_ip_bans_ip ON ip_bans(ip_address);
|
||||
CREATE INDEX idx_ip_bans_scope ON ip_bans(scope);
|
||||
CREATE INDEX idx_ip_bans_end_time ON ip_bans(end_time) WHERE end_time IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_ip_bans_active_lookup
|
||||
ON ip_bans(ip_address, scope);
|
||||
|
||||
CREATE INDEX idx_ban_history_target ON ban_history USING GIN (target);
|
||||
CREATE INDEX idx_ban_history_scope ON ban_history(scope);
|
||||
CREATE INDEX idx_ban_history_start_time ON ban_history(start_time DESC);
|
||||
|
||||
CREATE INDEX idx_appeals_ban_id ON appeals(ban_id);
|
||||
CREATE INDEX idx_appeals_sustained ON appeals(sustained);
|
||||
CREATE INDEX idx_appeals_appeal_date ON appeals(appeal_date DESC);
|
||||
CREATE INDEX idx_appeals_target ON appeals USING GIN (target);
|
||||
|
||||
CREATE UNIQUE INDEX idx_unique_active_player_ban
|
||||
ON player_bans(player_uuid, scope);
|
||||
|
||||
CREATE UNIQUE INDEX idx_unique_active_ip_ban
|
||||
ON ip_bans(ip_address, scope);
|
||||
|
||||
CREATE OR REPLACE FUNCTION archive_ban_on_delete()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO ban_history (target, reason, scope, operator_name, operator_uuid, start_time, end_time)
|
||||
VALUES (
|
||||
CASE TG_TABLE_NAME
|
||||
WHEN 'player_bans' THEN jsonb_build_object(
|
||||
'player_uuid', OLD.player_uuid,
|
||||
'player_name', OLD.player_name
|
||||
)
|
||||
WHEN 'ip_bans' THEN jsonb_build_object(
|
||||
'ip_address', host(OLD.ip_address)
|
||||
)
|
||||
END,
|
||||
OLD.reason,
|
||||
OLD.scope,
|
||||
OLD.operator_name,
|
||||
OLD.operator_uuid,
|
||||
OLD.start_time,
|
||||
OLD.end_time
|
||||
);
|
||||
RETURN OLD;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER archive_player_ban_on_delete
|
||||
BEFORE DELETE ON player_bans
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION archive_ban_on_delete();
|
||||
|
||||
CREATE TRIGGER archive_ip_ban_on_delete
|
||||
BEFORE DELETE ON ip_bans
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION archive_ban_on_delete();
|
||||
|
||||
-- Comments for documentation (written by sonnet 4.5)
|
||||
COMMENT ON TABLE player_bans IS 'Active player bans - deleted bans are moved to ban_history';
|
||||
COMMENT ON TABLE ip_bans IS 'Active IP bans - deleted bans are moved to ban_history';
|
||||
COMMENT ON TABLE ban_history IS 'Historical record of expired or revoked bans';
|
||||
COMMENT ON TABLE appeals IS 'Ban appeal requests and their outcomes';
|
||||
|
||||
COMMENT ON COLUMN player_bans.end_time IS 'NULL indicates permanent ban';
|
||||
COMMENT ON COLUMN player_bans.scope IS 'Ban scope: * for global, server name for specific server';
|
||||
|
||||
COMMENT ON COLUMN ip_bans.end_time IS 'NULL indicates permanent ban';
|
||||
COMMENT ON COLUMN ip_bans.scope IS 'Ban scope: * for global, server name for specific server';
|
||||
|
||||
COMMENT ON COLUMN ban_history.target IS 'JSONB containing player info (player_uuid, player_name) or IP info (ip_address)';
|
||||
COMMENT ON COLUMN ban_history.end_time IS 'NULL if ban was permanent and manually revoked';
|
||||
|
||||
COMMENT ON COLUMN appeals.sustained IS 'NULL = pending review, TRUE = ban kept (appeal rejected), FALSE = ban removed (appeal approved)';
|
||||
COMMENT ON COLUMN appeals.response IS 'Operator response to the appeal';
|
||||
COMMENT ON COLUMN appeals.target IS 'JSONB containing appellant contact info (player_uuid, player_name, email, discord, etc.)';
|
||||
53
src/main/resources/db/migration/V2__Fix_archive_triggers.sql
Normal file
53
src/main/resources/db/migration/V2__Fix_archive_triggers.sql
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
DROP TRIGGER IF EXISTS archive_player_ban_on_delete ON player_bans;
|
||||
DROP TRIGGER IF EXISTS archive_ip_ban_on_delete ON ip_bans;
|
||||
|
||||
DROP FUNCTION IF EXISTS archive_ban_on_delete();
|
||||
|
||||
CREATE OR REPLACE FUNCTION archive_player_ban_on_delete()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO ban_history (target, reason, scope, operator_name, operator_uuid, start_time, end_time)
|
||||
VALUES (
|
||||
jsonb_build_object(
|
||||
'player_uuid', OLD.player_uuid,
|
||||
'player_name', OLD.player_name
|
||||
),
|
||||
OLD.reason,
|
||||
OLD.scope,
|
||||
OLD.operator_name,
|
||||
OLD.operator_uuid,
|
||||
OLD.start_time,
|
||||
OLD.end_time
|
||||
);
|
||||
RETURN OLD;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION archive_ip_ban_on_delete()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO ban_history (target, reason, scope, operator_name, operator_uuid, start_time, end_time)
|
||||
VALUES (
|
||||
jsonb_build_object(
|
||||
'ip_address', host(OLD.ip_address)
|
||||
),
|
||||
OLD.reason,
|
||||
OLD.scope,
|
||||
OLD.operator_name,
|
||||
OLD.operator_uuid,
|
||||
OLD.start_time,
|
||||
OLD.end_time
|
||||
);
|
||||
RETURN OLD;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER archive_player_ban_on_delete
|
||||
BEFORE DELETE ON player_bans
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION archive_player_ban_on_delete();
|
||||
|
||||
CREATE TRIGGER archive_ip_ban_on_delete
|
||||
BEFORE DELETE ON ip_bans
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION archive_ip_ban_on_delete();
|
||||
4
src/main/resources/plugin.yml
Normal file
4
src/main/resources/plugin.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
name: BanHammer
|
||||
version: '0.1.0-SNAPSHOT'
|
||||
main: net.kuraczyk.banhammer.BanHammer
|
||||
api-version: '1.21'
|
||||
Loading…
Add table
Add a link
Reference in a new issue