diff --git a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlugin.java b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlugin.java index 1cab6a2..39b1f39 100644 --- a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlugin.java +++ b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlugin.java @@ -23,6 +23,7 @@ import dev.brighten.antivpn.database.VPNDatabase; import dev.brighten.antivpn.database.local.H2VPN; import dev.brighten.antivpn.database.mongo.MongoVPN; import dev.brighten.antivpn.database.sql.MySqlVPN; +import dev.brighten.antivpn.database.sql.PostgreSqlVPN; import dev.brighten.antivpn.loader.LoaderBootstrap; import lombok.Getter; import org.bstats.bukkit.Metrics; @@ -151,6 +152,8 @@ public class BukkitPlugin implements LoaderBootstrap { if(database instanceof MySqlVPN) { return "MySQL"; + } else if(database instanceof PostgreSqlVPN) { + return "PostgreSQL"; } else if(database instanceof H2VPN) { return "H2"; } else if(database instanceof MongoVPN) { diff --git a/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlugin.java b/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlugin.java index 15bb2ac..b57e1be 100644 --- a/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlugin.java +++ b/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlugin.java @@ -23,6 +23,7 @@ import dev.brighten.antivpn.database.VPNDatabase; import dev.brighten.antivpn.database.local.H2VPN; import dev.brighten.antivpn.database.mongo.MongoVPN; import dev.brighten.antivpn.database.sql.MySqlVPN; +import dev.brighten.antivpn.database.sql.PostgreSqlVPN; import dev.brighten.antivpn.loader.LoaderBootstrap; import lombok.Getter; import net.md_5.bungee.api.ProxyServer; @@ -87,6 +88,8 @@ public class BungeePlugin implements LoaderBootstrap { VPNDatabase database = AntiVPN.getInstance().getDatabase(); if(database instanceof MySqlVPN) { return "MySQL"; + } else if(database instanceof PostgreSqlVPN) { + return "PostgreSQL"; } else if(database instanceof H2VPN) { return "H2"; } else if(database instanceof MongoVPN) { diff --git a/Common/Source/build.gradle b/Common/Source/build.gradle index 6040092..f097c99 100644 --- a/Common/Source/build.gradle +++ b/Common/Source/build.gradle @@ -9,6 +9,7 @@ dependencies { implementation 'org.jetbrains:annotations:26.0.2' compileOnly 'com.mysql:mysql-connector-j:9.3.0' + compileOnly 'org.postgresql:postgresql:42.7.3' compileOnly 'com.h2database:h2:2.2.220' compileOnly 'com.github.ben-manes.caffeine:caffeine:3.1.8' compileOnly 'org.mongodb:mongo-java-driver:3.12.14' @@ -18,9 +19,11 @@ dependencies { testImplementation "org.testcontainers:testcontainers:2.0.4" testImplementation "org.testcontainers:testcontainers-junit-jupiter:2.0.4" testImplementation 'org.testcontainers:mysql:1.20.4' + testImplementation 'org.testcontainers:postgresql:1.20.4' testImplementation 'org.testcontainers:mongodb:1.20.4' testRuntimeOnly 'org.slf4j:slf4j-simple:2.0.16' testImplementation 'com.mysql:mysql-connector-j:9.3.0' + testImplementation 'org.postgresql:postgresql:42.7.3' testImplementation 'com.h2database:h2:2.2.220' testImplementation 'org.mongodb:mongo-java-driver:3.12.14' testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' @@ -35,6 +38,7 @@ shadowJar { relocate 'com.mongodb', 'dev.brighten.antivpn.shaded.com.mongodb' relocate 'com.mysql.cj', 'dev.brighten.antivpn.shaded.com.mysql.cj' relocate 'com.mysql.jdbc', 'dev.brighten.antivpn.shaded.com.mysql.jdbc' + relocate 'org.postgresql', 'dev.brighten.antivpn.shaded.org.postgresql' dependencies { exclude 'dev/brighten/antivpn/depends/Relocate*' diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java b/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java index 56a39f7..50d0ca7 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java @@ -25,6 +25,7 @@ import dev.brighten.antivpn.database.VPNDatabase; import dev.brighten.antivpn.database.local.H2VPN; import dev.brighten.antivpn.database.mongo.MongoVPN; import dev.brighten.antivpn.database.sql.MySqlVPN; +import dev.brighten.antivpn.database.sql.PostgreSqlVPN; import dev.brighten.antivpn.depends.LibraryLoader; import dev.brighten.antivpn.depends.MavenLibrary; import dev.brighten.antivpn.depends.Relocate; @@ -45,6 +46,7 @@ import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.List; +import java.util.Locale; @Getter @Setter(AccessLevel.PRIVATE) @@ -63,6 +65,14 @@ import java.util.List; @Relocate(from = "com.my\\" + "sql.jdbc", to = "dev.brighten.antivpn.shaded.com.mysql.jdbc"), } ) +@MavenLibrary( + groupId = "org.postgresql", + artifactId = "postgresql", + version = "42.7.3", + relocations = { + @Relocate(from = "org\\.postgresql", to = "dev.brighten.antivpn.shaded.org.postgresql"), + } +) @MavenLibrary(groupId = "com.\\github\\.ben-manes\\.caffeine", artifactId = "caffeine", version = "3.1.8", relocations = { @Relocate(from = "com\\.github\\.benmanes\\.caffeine", to = "dev.brighten.antivpn.shaded.com.github.benmanes.caffeine"), @@ -115,35 +125,7 @@ public class AntiVPN { INSTANCE.messageHandler = new MessageHandler(); try { - switch(INSTANCE.vpnConfig.getDatabaseType().toLowerCase()) { - case "h2": - case "local": - case "flatfile": { - AntiVPN.getInstance().getExecutor().log("Using databaseType H2..."); - INSTANCE.database = new H2VPN(); - INSTANCE.database.init(); - break; - } - case "mysql": - case "sql": { - AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL..."); - INSTANCE.database = new MySqlVPN(); - INSTANCE.database.init(); - break; - } - case "mongo": - case "mongodb": - case "mongod": { - INSTANCE.database = new MongoVPN(); - INSTANCE.database.init(); - break; - } - default: { - AntiVPN.getInstance().getExecutor().log("Could not find database type \"" + INSTANCE.vpnConfig.getDatabaseType() + "\". " + - "Options: [MySQL]"); - break; - } - } + INSTANCE.initializeDatabase(); } catch (Exception e) { AntiVPN.getInstance().getExecutor().logException("Could not initialize database, plugin disabling...", e); executor.disablePlugin(); @@ -192,7 +174,7 @@ public class AntiVPN { } public void stop() { - if (database instanceof H2VPN) { + if (database != null && database.getClass() == H2VPN.class) { database.shutdown(); // Try to deregister driver @@ -216,36 +198,7 @@ public class AntiVPN { public void reloadDatabase() { database.shutdown(); - - switch(AntiVPN.getInstance().getVpnConfig().getDatabaseType().toLowerCase()) { - case "h2": - case "local": - case "flatfile": { - AntiVPN.getInstance().getExecutor().log("Using databaseType H2..."); - INSTANCE.database = new H2VPN(); - INSTANCE.database.init(); - break; - } - case "mysql": - case "sql":{ - AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL..."); - INSTANCE.database = new MySqlVPN(); - INSTANCE.database.init(); - break; - } - case "mongo": - case "mongodb": - case "mongod": { - INSTANCE.database = new MongoVPN(); - INSTANCE.database.init(); - break; - } - default: { - AntiVPN.getInstance().getExecutor().log("Could not find database type \"" + INSTANCE.vpnConfig.getDatabaseType() + "\". " + - "Options: [MySQL]"); - break; - } - } + initializeDatabase(); } public static AntiVPN getInstance() { @@ -276,4 +229,37 @@ public class AntiVPN { private void registerCommands() { commands.add(new AntiVPNCommand()); } + + private void initializeDatabase() { + database = createConfiguredDatabase(); + database.init(); + } + + private VPNDatabase createConfiguredDatabase() { + String configuredType = vpnConfig.getDatabaseType(); + String databaseType = configuredType.toLowerCase(Locale.ROOT); + + return switch (databaseType) { + case "h2", "local", "flatfile" -> { + getExecutor().log("Using databaseType H2..."); + yield new H2VPN(); + } + case "mysql", "sql" -> { + getExecutor().log("Using databaseType MySQL..."); + yield new MySqlVPN(); + } + case "postgres", "postgresql", "psql" -> { + getExecutor().log("Using databaseType PostgreSQL..."); + yield new PostgreSqlVPN(); + } + case "mongo", "mongodb", "mongod" -> { + getExecutor().log("Using databaseType MongoDB..."); + yield new MongoVPN(); + } + default -> throw new IllegalArgumentException( + "Could not find database type \"" + configuredType + + "\". Options: [H2, MySQL, PostgreSQL, MongoDB]" + ); + }; + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/api/VPNConfig.java b/Common/Source/src/main/java/dev/brighten/antivpn/api/VPNConfig.java index 86e84e0..6c5903d 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/api/VPNConfig.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/api/VPNConfig.java @@ -175,6 +175,10 @@ public class VPNConfig { case "mongo": case "mongod": return 27017; + case "postgresql": + case "postgres": + case "psql": + return 5432; case "sql": case "mysql": return 3306; @@ -222,4 +226,4 @@ public class VPNConfig { countryVanillaKickReason = defaultCountryKickReason.get(); } -} \ No newline at end of file +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/First.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/First.java index 7b78bbb..0713f67 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/First.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/First.java @@ -88,11 +88,11 @@ public class First implements Version { return; } - closeOnEnd(Query.prepare(String.format( - "drop index `%s` on `%s`", - indexName, - tableName - ))).execute(); + String sql = Query.getDialect().isPostgreSql() + ? String.format("drop index \"%s\"", indexName) + : String.format("drop index `%s` on `%s`", indexName, tableName); + + closeOnEnd(Query.prepare(sql)).execute(); } protected boolean hasIndex(String tableName, String indexName) throws SQLException { diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Second.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Second.java index fd81080..2318e77 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Second.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Second.java @@ -21,6 +21,7 @@ import dev.brighten.antivpn.database.DatabaseException; import dev.brighten.antivpn.database.VPNDatabase; import dev.brighten.antivpn.database.local.H2VPN; import dev.brighten.antivpn.database.sql.MySqlVPN; +import dev.brighten.antivpn.database.sql.PostgreSqlVPN; import dev.brighten.antivpn.database.sql.utils.ExecutableStatement; import dev.brighten.antivpn.database.sql.utils.Query; import dev.brighten.antivpn.database.version.Version; @@ -37,7 +38,9 @@ public class Second extends First implements Version { @Override public void update(VPNDatabase database) throws DatabaseException { - if(database instanceof H2VPN h2VPN && !(database instanceof MySqlVPN)) { + if(database instanceof H2VPN h2VPN + && !(database instanceof MySqlVPN) + && !(database instanceof PostgreSqlVPN)) { h2VPN.backupDatabase(); } List whitelistedIps = new ArrayList<>(); diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/MySqlVPN.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/MySqlVPN.java index 54ac223..b23d971 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/MySqlVPN.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/MySqlVPN.java @@ -21,32 +21,14 @@ import dev.brighten.antivpn.database.local.H2VPN; import dev.brighten.antivpn.database.sql.utils.MySQL; import dev.brighten.antivpn.database.version.Version; -import java.util.concurrent.TimeUnit; - public class MySqlVPN extends H2VPN { - public MySqlVPN() { - AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> { - if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; - - //Refreshing whitelisted players - AntiVPN.getInstance().getExecutor().getWhitelisted().clear(); - AntiVPN.getInstance().getExecutor().getWhitelisted() - .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted()); - - //Refreshing whitlisted IPs - AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear(); - AntiVPN.getInstance().getExecutor().getWhitelistedIps() - .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()); - }, 2, 30, TimeUnit.SECONDS); - } - @Override public void init() { if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return; AntiVPN.getInstance().getExecutor().log("Initializing MySQL..."); - MySQL.init(); + MySQL.initMySql(); AntiVPN.getInstance().getExecutor().log("Checking for updates..."); diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/PostgreSqlVPN.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/PostgreSqlVPN.java new file mode 100644 index 0000000..a4c9eeb --- /dev/null +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/PostgreSqlVPN.java @@ -0,0 +1,47 @@ +/* + * Copyright 2026 Dawson Hessler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.brighten.antivpn.database.sql; + +import dev.brighten.antivpn.AntiVPN; +import dev.brighten.antivpn.database.local.H2VPN; +import dev.brighten.antivpn.database.sql.utils.MySQL; +import dev.brighten.antivpn.database.version.Version; + +public class PostgreSqlVPN extends H2VPN { + + @Override + public void init() { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) { + return; + } + + AntiVPN.getInstance().getExecutor().log("Initializing PostgreSQL..."); + MySQL.initPostgreSql(); + + AntiVPN.getInstance().getExecutor().log("Checking for updates..."); + + try { + for (Version version : Version.postgresVersions) { + if (version.needsUpdate(this)) { + version.update(this); + } + } + } catch (Exception e) { + throw new RuntimeException("Could not complete version setup due to SQL error", e); + } + } +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java index 662ef70..b181044 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java @@ -20,10 +20,12 @@ import com.mysql.cj.jdbc.Driver; import dev.brighten.antivpn.AntiVPN; import org.h2.jdbc.JdbcSQLFeatureNotSupportedException; import org.h2.jdbc.JdbcSQLNonTransientConnectionException; +import org.postgresql.util.PSQLException; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.sql.Statement; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLSyntaxErrorException; @@ -34,6 +36,10 @@ public class MySQL { private static Connection conn; public static void init() { + initMySql(); + } + + public static void initMySql() { try { if (conn == null || conn.isClosed()) { String url = "jdbc:mysql://" + AntiVPN.getInstance().getVpnConfig().getIp() @@ -72,12 +78,90 @@ public class MySQL { } } + public static void initPostgreSql() { + try { + if (conn == null || conn.isClosed()) { + Properties properties = new Properties(); + properties.setProperty("user", AntiVPN.getInstance().getVpnConfig().getUsername()); + properties.setProperty("password", AntiVPN.getInstance().getVpnConfig().getPassword()); + + String host = AntiVPN.getInstance().getVpnConfig().getIp(); + int port = AntiVPN.getInstance().getVpnConfig().getPort(); + String databaseName = AntiVPN.getInstance().getVpnConfig().getDatabaseName(); + + String adminUrl = "jdbc:postgresql://" + host + ":" + port + "/postgres"; + try (Connection adminConnection = new org.postgresql.Driver().connect(adminUrl, properties)) { + if (adminConnection == null) { + throw new SQLException("PostgreSQL driver did not accept URL: " + adminUrl); + } + + adminConnection.setAutoCommit(true); + ensurePostgreSqlDatabase(adminConnection, databaseName); + } + + String databaseUrl = "jdbc:postgresql://" + host + ":" + port + "/" + databaseName; + conn = new org.postgresql.Driver().connect(databaseUrl, properties); + if (conn == null) { + throw new SQLException("PostgreSQL driver did not accept URL: " + databaseUrl); + } + + conn.setAutoCommit(true); + Query.use(conn); + AntiVPN.getInstance().getExecutor().log("Connection to PostgreSQL has been established."); + } + } catch (Exception e) { + AntiVPN.getInstance().getExecutor().logException("Failed to load postgresql: " + e.getMessage(), e); + throw new RuntimeException("Could not initialize PostgreSQL connection", e); + } + } + private static boolean isDatabaseCreationPermissionIssue(SQLException ex) { return ex instanceof SQLSyntaxErrorException && ex.getMessage() != null && ex.getMessage().contains("Access denied"); } + private static void ensurePostgreSqlDatabase(Connection adminConnection, String databaseName) throws SQLException { + try (var statement = adminConnection.prepareStatement("SELECT 1 FROM pg_database WHERE datname = ?")) { + statement.setString(1, databaseName); + + try (var resultSet = statement.executeQuery()) { + if (resultSet.next()) { + return; + } + } + } + + try (Statement statement = adminConnection.createStatement()) { + statement.execute("CREATE DATABASE " + quotePostgreSqlIdentifier(databaseName)); + } catch (SQLException ex) { + if (isPostgreSqlAlreadyExists(ex)) { + return; + } + + if (!isPostgreSqlCreationPermissionIssue(ex)) { + throw ex; + } + + AntiVPN.getInstance().getExecutor().log( + "No permission to create PostgreSQL database \"" + databaseName + + "\". Attempting to use the existing database instead." + ); + } + } + + private static boolean isPostgreSqlAlreadyExists(SQLException ex) { + return ex instanceof PSQLException && "42P04".equals(ex.getSQLState()); + } + + private static boolean isPostgreSqlCreationPermissionIssue(SQLException ex) { + return "42501".equals(ex.getSQLState()); + } + + private static String quotePostgreSqlIdentifier(String identifier) { + return "\"" + identifier.replace("\"", "\"\"") + "\""; + } + public static void initH2() { initH2(true); } @@ -174,8 +258,9 @@ public class MySQL { if(conn instanceof NonClosableConnection) { ((NonClosableConnection)conn).shutdown(); } else conn.close(); - conn = null; } + conn = null; + Query.use(null); } catch (Exception e) { AntiVPN.getInstance().getExecutor().logException(e); } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/Query.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/Query.java index 8c790e7..88446a5 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/Query.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/Query.java @@ -25,14 +25,17 @@ import java.sql.SQLException; public class Query { @Getter private static Connection conn; + @Getter + private static SqlDialect dialect = SqlDialect.GENERIC; public static void use(Connection conn) { Query.conn = conn; + Query.dialect = SqlDialect.from(conn); } @SuppressWarnings("SqlSourceToSinkFlow") public static ExecutableStatement prepare(@Language("SQL") String sql) throws SQLException { - return new ExecutableStatement(conn.prepareStatement(sql)); + return new ExecutableStatement(conn.prepareStatement(dialect.translate(sql))); } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/SqlDialect.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/SqlDialect.java new file mode 100644 index 0000000..8b10e2e --- /dev/null +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/SqlDialect.java @@ -0,0 +1,78 @@ +/* + * Copyright 2026 Dawson Hessler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.brighten.antivpn.database.sql.utils; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Locale; + +public enum SqlDialect { + GENERIC, + H2, + MYSQL, + POSTGRESQL; + + public static SqlDialect from(Connection connection) { + if (connection == null) { + return GENERIC; + } + + try { + String productName = connection.getMetaData().getDatabaseProductName(); + if (productName == null) { + return GENERIC; + } + + return from(productName); + } catch (SQLException ignored) { + return GENERIC; + } + } + + public static SqlDialect from(String productName) { + String normalized = productName.toLowerCase(Locale.ROOT); + if (normalized.contains("postgres")) { + return POSTGRESQL; + } + if (normalized.contains("mysql")) { + return MYSQL; + } + if (normalized.contains("h2")) { + return H2; + } + return GENERIC; + } + + public boolean isPostgreSql() { + return this == POSTGRESQL; + } + + public String translate(String sql) { + if (!isPostgreSql()) { + return sql; + } + + String translated = sql.replace('`', '"'); + translated = translated.replaceAll( + "(?i)\\bINT\\s+AUTO_INCREMENT\\s+PRIMARY\\s+KEY\\b", + "INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY" + ); + translated = translated.replaceAll("(?i)\\bAUTO_INCREMENT\\b", "GENERATED BY DEFAULT AS IDENTITY"); + translated = translated.replaceAll("(?i)\\bdouble\\b(?!\\s+precision)", "double precision"); + return translated; + } +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/version/Version.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/version/Version.java index 2c714b4..d3d2f86 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/version/Version.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/version/Version.java @@ -26,6 +26,7 @@ import dev.brighten.antivpn.database.mongo.version.MongoFirst; import dev.brighten.antivpn.database.mongo.version.MongoSecond; import dev.brighten.antivpn.database.mongo.version.MongoThird; import dev.brighten.antivpn.database.sql.MySqlVPN; +import dev.brighten.antivpn.database.sql.PostgreSqlVPN; import dev.brighten.antivpn.database.sql.version.MySQLFirst; @@ -36,5 +37,6 @@ public interface Version { Version[] mongoDbVersions = new Version[] {new MongoFirst(), new MongoSecond(), new MongoThird()}; Version[] mysqlVersions = new Version[] {new MySQLFirst(), new Second(), new Third()}; + Version[] postgresVersions = new Version[] {new First(), new Second(), new Third()}; Version[] h2Versions = new Version[] {new First(), new Second(), new Third()}; -} \ No newline at end of file +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java b/Common/Source/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java index 7a76d45..4899f35 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java @@ -325,7 +325,9 @@ public final class LibraryLoader { return className; } - if (className.startsWith("com.mysql.cj") || className.startsWith("com.mysql.jdbc")) { + if (className.startsWith("com.mysql.cj") + || className.startsWith("com.mysql.jdbc") + || className.startsWith("org.postgresql")) { return "dev.brighten.antivpn.shaded." + className; } diff --git a/Common/Source/src/main/resources/config.yml b/Common/Source/src/main/resources/config.yml index 4827f1e..371ad3e 100644 --- a/Common/Source/src/main/resources/config.yml +++ b/Common/Source/src/main/resources/config.yml @@ -18,7 +18,7 @@ database: # Enable to cache queries and save alerts state beyond restarts enabled: true useCredentials: false - #Options Mongo, MySQL, or H2 + #Options Mongo, MySQL, PostgreSQL, or H2 type: H2 # The database name you would like to use database: kaurivpn @@ -30,7 +30,7 @@ database: password: password # The IP of your database goes here ip: localhost - # -1 will use default port of databases (MySQL:3306, Mongo:27017). Otherwise, enter alternative ports here. + # -1 will use default port of databases (MySQL:3306, PostgreSQL:5432, Mongo:27017). Otherwise, enter an alternative port here. port: -1 commands: # Enable this to override the default kick function of the plugin with your own commands diff --git a/Common/Source/src/test/java/dev/brighten/antivpn/database/MongoDatabaseIntegrationTest.java b/Common/Source/src/test/java/dev/brighten/antivpn/database/MongoDatabaseIntegrationTest.java index 133c593..abfbca4 100644 --- a/Common/Source/src/test/java/dev/brighten/antivpn/database/MongoDatabaseIntegrationTest.java +++ b/Common/Source/src/test/java/dev/brighten/antivpn/database/MongoDatabaseIntegrationTest.java @@ -14,7 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; -@Testcontainers +@Testcontainers(disabledWithoutDocker = true) class MongoDatabaseIntegrationTest extends DatabaseIntegrationTestSupport { @Container diff --git a/Common/Source/src/test/java/dev/brighten/antivpn/database/MySqlDatabaseIntegrationTest.java b/Common/Source/src/test/java/dev/brighten/antivpn/database/MySqlDatabaseIntegrationTest.java index 8e2898f..63192a7 100644 --- a/Common/Source/src/test/java/dev/brighten/antivpn/database/MySqlDatabaseIntegrationTest.java +++ b/Common/Source/src/test/java/dev/brighten/antivpn/database/MySqlDatabaseIntegrationTest.java @@ -11,7 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; -@Testcontainers +@Testcontainers(disabledWithoutDocker = true) class MySqlDatabaseIntegrationTest extends DatabaseIntegrationTestSupport { @Container diff --git a/Common/Source/src/test/java/dev/brighten/antivpn/database/PostgreSqlDatabaseIntegrationTest.java b/Common/Source/src/test/java/dev/brighten/antivpn/database/PostgreSqlDatabaseIntegrationTest.java new file mode 100644 index 0000000..ce193fa --- /dev/null +++ b/Common/Source/src/test/java/dev/brighten/antivpn/database/PostgreSqlDatabaseIntegrationTest.java @@ -0,0 +1,47 @@ +package dev.brighten.antivpn.database; + +import dev.brighten.antivpn.database.sql.PostgreSqlVPN; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.sql.DriverManager; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +@Testcontainers(disabledWithoutDocker = true) +class PostgreSqlDatabaseIntegrationTest extends DatabaseIntegrationTestSupport { + + @Container + private static final PostgreSQLContainer POSTGRES = new PostgreSQLContainer<>("postgres:16.3") + .withDatabaseName("antivpn") + .withUsername("testuser") + .withPassword("testpass"); + + @Test + void postgreSqlDatabaseImplementsTheVpnDatabaseContract() throws Exception { + assertTrue(POSTGRES.isRunning(), "PostgreSQL Testcontainer should be running"); + + try (var connection = DriverManager.getConnection( + POSTGRES.getJdbcUrl(), + POSTGRES.getUsername(), + POSTGRES.getPassword() + ); + var statement = connection.createStatement(); + var resultSet = statement.executeQuery("SELECT 1")) { + assertTrue(resultSet.next(), "Expected a row from the PostgreSQL container"); + assertEquals(1, resultSet.getInt(1), "Expected PostgreSQL container to respond to SELECT 1"); + } + + when(vpnConfig.getIp()).thenReturn(POSTGRES.getHost()); + when(vpnConfig.getPort()).thenReturn(POSTGRES.getMappedPort(5432)); + when(vpnConfig.getDatabaseName()).thenReturn(POSTGRES.getDatabaseName()); + when(vpnConfig.getUsername()).thenReturn(POSTGRES.getUsername()); + when(vpnConfig.getPassword()).thenReturn(POSTGRES.getPassword()); + + assertDatabaseContract(new PostgreSqlVPN()); + } +} diff --git a/Sponge/SpongePlugin/build.gradle b/Sponge/SpongePlugin/build.gradle index 3e50f0a..499a578 100644 --- a/Sponge/SpongePlugin/build.gradle +++ b/Sponge/SpongePlugin/build.gradle @@ -37,6 +37,7 @@ shadowJar { relocate 'com.mongodb', 'dev.brighten.antivpn.shaded.com.mongodb' relocate 'com.mysql.cj', 'dev.brighten.antivpn.shaded.com.mysql.cj' relocate 'com.mysql.jdbc', 'dev.brighten.antivpn.shaded.com.mysql.jdbc' + relocate 'org.postgresql', 'dev.brighten.antivpn.shaded.org.postgresql' } tasks.build.dependsOn shadowJar diff --git a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java index 5f76d1d..1b17d74 100644 --- a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java +++ b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java @@ -23,6 +23,7 @@ import dev.brighten.antivpn.database.VPNDatabase; import dev.brighten.antivpn.database.local.H2VPN; import dev.brighten.antivpn.database.mongo.MongoVPN; import dev.brighten.antivpn.database.sql.MySqlVPN; +import dev.brighten.antivpn.database.sql.PostgreSqlVPN; import dev.brighten.antivpn.loader.LoaderBootstrap; import dev.brighten.antivpn.velocity.command.VelocityCommand; import lombok.Getter; @@ -61,6 +62,8 @@ public class VelocityPlugin implements LoaderBootstrap { VPNDatabase database = AntiVPN.getInstance().getDatabase(); if(database instanceof MySqlVPN) { return "MySQL"; + } else if(database instanceof PostgreSqlVPN) { + return "PostgreSQL"; } else if(database instanceof H2VPN) { return "H2"; } else if(database instanceof MongoVPN) { diff --git a/build.gradle b/build.gradle index daf06ef..4b47bcc 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,7 @@ allprojects { version = '1.10.0' repositories { + mavenCentral() maven { url 'https://repo.papermc.io/repository/maven-public/' } maven { url 'https://nexus.funkemunky.cc/repository/papermc-public/' } maven { url 'https://nexus.funkemunky.cc/repository/maven-public/' }