diff --git a/README.MD b/README.MD
index 5067593..4fc90a0 100644
--- a/README.MD
+++ b/README.MD
@@ -8,6 +8,7 @@ It handles:
- ACL Cleanup
- Bouncer Restart
+- Dev Rank Handling
- IP2ASN
- Profiler Daemon Ingest
- Profiler DB Cleanup
@@ -20,6 +21,9 @@ Removes entries from a HAproxy ACL via its socket interface in order for inactiv
Restarts our "bouncer" server every day. This is the thing that does 2FA, server queue, and region redirection.
+### Dev Rank
+Adds or removes members from development rank when changes are made. Performed hourly.
+
### IP2ASN
Caches ASNs for IP addresses of recent players.
diff --git a/TaskDaemon.Core/pom.xml b/TaskDaemon.Core/pom.xml
index f886b4e..4692f8b 100644
--- a/TaskDaemon.Core/pom.xml
+++ b/TaskDaemon.Core/pom.xml
@@ -12,6 +12,11 @@
TaskDaemon.Core
+
+ me.aa07.paradise
+ taskdaemon-database-forums
+ dev-SNAPSHOT
+
me.aa07.paradise
taskdaemon-database-gamedb
@@ -73,6 +78,7 @@
validate
+ validate
checkstyle.xml
true
diff --git a/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/Core.java b/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/Core.java
index a4147b4..f249f24 100644
--- a/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/Core.java
+++ b/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/Core.java
@@ -7,6 +7,7 @@
import me.aa07.paradise.taskdaemon.core.database.DbCore;
import me.aa07.paradise.taskdaemon.core.modules.aclcleanup.AclCleanupJob;
import me.aa07.paradise.taskdaemon.core.modules.bouncerrestart.BouncerRestartJob;
+import me.aa07.paradise.taskdaemon.core.modules.devrank.DevRankJob;
import me.aa07.paradise.taskdaemon.core.modules.ip2asn.Ip2AsnJob;
import me.aa07.paradise.taskdaemon.core.modules.profilercleanup.ProfilerCleanupJob;
import me.aa07.paradise.taskdaemon.core.modules.profileringest.ProfilerWorker;
@@ -101,13 +102,29 @@ private void setupJobs(Scheduler scheduler, DbCore dbCore, ConfigHolder config,
jdm_bouncerrestart.put("LOGGER", logger);
jdm_bouncerrestart.put("TGS_CFG", config.tgs);
JobDetail jd_bouncerrestart = JobBuilder.newJob(BouncerRestartJob.class)
- .withIdentity("bouncerrestart", "bouncerrestart")
- .usingJobData(jdm_bouncerrestart)
- .build();
+ .withIdentity("bouncerrestart", "bouncerrestart")
+ .usingJobData(jdm_bouncerrestart)
+ .build();
CronTrigger ct_bouncerrestart = TriggerBuilder.newTrigger()
- .withIdentity("bouncerrestart", "bouncerrestart")
- .withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 * * ?")) // Every day - midnight
- .build();
+ .withIdentity("bouncerrestart", "bouncerrestart")
+ .withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 * * ?")) // Every day - midnight
+ .build();
+
+ // DevRank update
+ JobDataMap jdm_devrank = new JobDataMap();
+ jdm_devrank.put("LOGGER", logger);
+ jdm_devrank.put("DBCORE", dbCore);
+
+ JobDetail jd_devrank = JobBuilder.newJob(DevRankJob.class)
+ .withIdentity("devrank", "devrank")
+ .usingJobData(jdm_devrank)
+ .build();
+
+ CronTrigger ct_devrank = TriggerBuilder.newTrigger()
+ .withIdentity("devrank", "devrank")
+ .withSchedule(CronScheduleBuilder.cronSchedule("0 0 * * * ?")) // Every hour on the hour
+ .build();
+
// IP2ASN
JobDataMap jdm_ip2asn = new JobDataMap();
@@ -115,13 +132,13 @@ private void setupJobs(Scheduler scheduler, DbCore dbCore, ConfigHolder config,
jdm_ip2asn.put("DBCORE", dbCore);
jdm_ip2asn.put("IP2ASNCFG", config.ip2asn);
JobDetail jd_ip2asn = JobBuilder.newJob(Ip2AsnJob.class)
- .withIdentity("ip2asn", "ip2asn")
- .usingJobData(jdm_ip2asn)
- .build();
+ .withIdentity("ip2asn", "ip2asn")
+ .usingJobData(jdm_ip2asn)
+ .build();
CronTrigger ct_ip2asn = TriggerBuilder.newTrigger()
- .withIdentity("ip2asn", "ip2asn")
- .withSchedule(CronScheduleBuilder.cronSchedule("0 */10 * * * ?")) // Every 10 minutes
- .build();
+ .withIdentity("ip2asn", "ip2asn")
+ .withSchedule(CronScheduleBuilder.cronSchedule("0 */10 * * * ?")) // Every 10 minutes
+ .build();
// Profiler cleanup
JobDataMap jdm_profilercleanup = new JobDataMap();
@@ -139,6 +156,7 @@ private void setupJobs(Scheduler scheduler, DbCore dbCore, ConfigHolder config,
// Schedule all
scheduler.scheduleJob(jd_aclcleanup, ct_aclcleanup);
scheduler.scheduleJob(jd_bouncerrestart, ct_bouncerrestart);
+ scheduler.scheduleJob(jd_devrank, ct_devrank);
scheduler.scheduleJob(jd_ip2asn, ct_ip2asn);
scheduler.scheduleJob(jd_profilercleanup, ct_profilercleanup);
}
diff --git a/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/config/ConfigHolder.java b/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/config/ConfigHolder.java
index d37353b..538486c 100644
--- a/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/config/ConfigHolder.java
+++ b/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/config/ConfigHolder.java
@@ -1,6 +1,7 @@
package me.aa07.paradise.taskdaemon.core.config;
public class ConfigHolder {
+ public DatabaseConfig forumsDatabase;
public DatabaseConfig gameDatabase;
public Ip2AsnSerivceConfig ip2asn;
public PfsenseConfig pfsense;
diff --git a/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/config/PfsenseConfig.java b/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/config/PfsenseConfig.java
index 8d05331..844f2ea 100644
--- a/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/config/PfsenseConfig.java
+++ b/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/config/PfsenseConfig.java
@@ -1,6 +1,6 @@
package me.aa07.paradise.taskdaemon.core.config;
-public class PfsenseConfig{
+public class PfsenseConfig {
public String host;
public int port;
}
diff --git a/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/database/DatabaseType.java b/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/database/DatabaseType.java
index ab125ca..8fa95ba 100644
--- a/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/database/DatabaseType.java
+++ b/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/database/DatabaseType.java
@@ -1,5 +1,5 @@
package me.aa07.paradise.taskdaemon.core.database;
public enum DatabaseType {
- ProfilerDb, GameDb
+ ProfilerDb, GameDb, Forums
}
diff --git a/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/database/DbCore.java b/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/database/DbCore.java
index 06b738e..c057595 100644
--- a/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/database/DbCore.java
+++ b/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/database/DbCore.java
@@ -51,6 +51,7 @@ private DataSource openDataSource(String url, String username, String password)
private void establishConnections(ConfigHolder config) {
HashMap db_types = new HashMap();
+ db_types.put(DatabaseType.Forums, config.forumsDatabase);
db_types.put(DatabaseType.GameDb, config.gameDatabase);
db_types.put(DatabaseType.ProfilerDb, config.profilerDatabase);
diff --git a/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/modules/aclcleanup/AclCleanupJob.java b/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/modules/aclcleanup/AclCleanupJob.java
index 41277f4..d397c59 100644
--- a/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/modules/aclcleanup/AclCleanupJob.java
+++ b/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/modules/aclcleanup/AclCleanupJob.java
@@ -8,7 +8,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
-
import me.aa07.paradise.taskdaemon.core.config.PfsenseConfig;
import me.aa07.paradise.taskdaemon.core.database.DatabaseType;
import me.aa07.paradise.taskdaemon.core.database.DbCore;
diff --git a/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/modules/devrank/DevRankJob.java b/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/modules/devrank/DevRankJob.java
new file mode 100644
index 0000000..f4e3b58
--- /dev/null
+++ b/TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/modules/devrank/DevRankJob.java
@@ -0,0 +1,172 @@
+package me.aa07.paradise.taskdaemon.core.modules.devrank;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import me.aa07.paradise.taskdaemon.core.database.DatabaseType;
+import me.aa07.paradise.taskdaemon.core.database.DbCore;
+import me.aa07.paradise.taskdaemon.database.forums.Tables;
+import me.aa07.paradise.taskdaemon.database.gamedb.tables.Admin;
+import org.apache.logging.log4j.Logger;
+import org.jooq.DSLContext;
+import org.jooq.Record;
+import org.jooq.Result;
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+
+/**
+ * Scheduled job that syncs dev team permissions between forums and game
+ * databases.
+ */
+
+public class DevRankJob implements Job {
+ private static final int DEV_TEAM_GROUP = 39;
+ private static final int DEV_TEAM_BITFLAG = 262144;
+
+ @Override
+ public void execute(JobExecutionContext context) throws JobExecutionException {
+ JobDataMap data_map = context.getMergedJobDataMap();
+
+ // Get logger and db
+ Logger logger = (Logger) data_map.get("LOGGER");
+ DbCore dbcore = (DbCore) data_map.get("DBCORE");
+
+ if (logger == null || dbcore == null) {
+ System.err.println("[DevRank] LOGGER or DBCORE was not set in JobDataMap.");
+ return;
+ }
+
+ logger.info("[DevRank] Starting dev rank sync");
+
+ try {
+ DSLContext forums_db = dbcore.jooq(DatabaseType.Forums);
+ DSLContext game_db = dbcore.jooq(DatabaseType.GameDb);
+
+ // Collect all dev team ckeys from forums database
+ List dev_team_ckeys = new ArrayList<>();
+
+ Result extends Record> forum_records = forums_db.select(
+ Tables.CORE_MEMBERS.MEMBER_ID,
+ Tables.CORE_MEMBERS.NAME,
+ Tables.CORE_MEMBERS.MEMBER_GROUP_ID,
+ Tables.CORE_MEMBERS.MGROUP_OTHERS,
+ Tables.CORE_PFIELDS_CONTENT.FIELD_10).from(Tables.CORE_MEMBERS)
+ .leftJoin(Tables.CORE_PFIELDS_CONTENT)
+ .on(Tables.CORE_MEMBERS.MEMBER_ID.eq(Tables.CORE_PFIELDS_CONTENT.MEMBER_ID))
+ .fetch();
+
+ for (Record rec : forum_records) {
+ int primary_group = rec.get(Tables.CORE_MEMBERS.MEMBER_GROUP_ID);
+ String other_groups = rec.get(Tables.CORE_MEMBERS.MGROUP_OTHERS);
+ String ckey = rec.get(Tables.CORE_PFIELDS_CONTENT.FIELD_10);
+
+ Set all_groups = new HashSet();
+ all_groups.add(primary_group);
+
+ if (other_groups != null && !other_groups.isBlank()) {
+ Arrays.stream(other_groups.split(","))
+ .filter(g -> !g.isBlank())
+ .map(Integer::parseInt)
+ .forEach(all_groups::add);
+ }
+
+ if (all_groups.contains(DEV_TEAM_GROUP)) {
+ if (ckey == null || ckey.isBlank()) {
+ logger.warn("[DevRank] Forums user {} (ID {}) has no linked ckey",
+ rec.get(Tables.CORE_MEMBERS.NAME), rec.get(Tables.CORE_MEMBERS.MEMBER_ID));
+ continue;
+ }
+
+ String cleaned = ckey.toLowerCase().replaceAll("[\\s_\\-]", "");
+ dev_team_ckeys.add(cleaned);
+ }
+ }
+
+ // Load all ingame admins
+ Map ingame_admins = new HashMap<>();
+
+ Result extends Record> admin_records = game_db.select(
+ Admin.ADMIN.ID,
+ Admin.ADMIN.CKEY,
+ Admin.ADMIN.ADMIN_RANK,
+ Admin.ADMIN.FLAGS).from(Admin.ADMIN).fetch();
+
+ for (Record rec : admin_records) {
+ String ckey = rec.get(Admin.ADMIN.CKEY);
+ ingame_admins.put(ckey, new AdminEntry(
+ rec.get(Admin.ADMIN.ID),
+ rec.get(Admin.ADMIN.ADMIN_RANK),
+ rec.get(Admin.ADMIN.FLAGS)));
+ }
+
+ // Apply permissions to those who need them
+ for (String ckey : dev_team_ckeys) {
+ AdminEntry entry = ingame_admins.get(ckey);
+
+ if (entry == null) {
+ logger.info("[DevRank] Ckey {} not in admin table, adding new dev", ckey);
+ game_db.insertInto(Admin.ADMIN)
+ .set(Admin.ADMIN.CKEY, ckey)
+ .set(Admin.ADMIN.ADMIN_RANK, "Developer")
+ .set(Admin.ADMIN.FLAGS, DEV_TEAM_BITFLAG)
+ .execute();
+ } else {
+ if ("Removed".equals(entry.rank()) || entry.flags() == 0) {
+ logger.info("[DevRank] Resetting {} to dev team with new flag", ckey);
+ game_db.update(Admin.ADMIN)
+ .set(Admin.ADMIN.ADMIN_RANK, "Developer")
+ .set(Admin.ADMIN.FLAGS, DEV_TEAM_BITFLAG)
+ .where(Admin.ADMIN.ID.eq(entry.id()))
+ .execute();
+ } else if ((entry.flags() & DEV_TEAM_BITFLAG) == 0) {
+ logger.info("[DevRank] Adding dev flag to {}", ckey);
+ game_db.update(Admin.ADMIN)
+ .set(Admin.ADMIN.FLAGS, entry.flags() + DEV_TEAM_BITFLAG)
+ .where(Admin.ADMIN.ID.eq(entry.id()))
+ .execute();
+ } else {
+ logger.debug("[DevRank] {} already has dev flag", ckey);
+ }
+ }
+ }
+
+ // Remove devteam flag from those who no longer qualify
+ for (Map.Entry entry : ingame_admins.entrySet()) {
+ String ckey = entry.getKey();
+ AdminEntry admin = entry.getValue();
+
+ if (!dev_team_ckeys.contains(ckey)) {
+ if (admin.flags() == DEV_TEAM_BITFLAG) {
+ logger.info("[DevRank] {} only had dev flag, removing rank and flags", ckey);
+ game_db.update(Admin.ADMIN)
+ .set(Admin.ADMIN.ADMIN_RANK, "Removed")
+ .set(Admin.ADMIN.FLAGS, 0)
+ .where(Admin.ADMIN.ID.eq(admin.id()))
+ .execute();
+ } else if ((admin.flags() & DEV_TEAM_BITFLAG) != 0) {
+ logger.info("[DevRank] {} no longer in dev team, removing dev flag only", ckey);
+ game_db.update(Admin.ADMIN)
+ .set(Admin.ADMIN.FLAGS, admin.flags() - DEV_TEAM_BITFLAG)
+ .where(Admin.ADMIN.ID.eq(admin.id()))
+ .execute();
+ }
+ }
+ }
+
+ logger.info("[DevRank] Finished sync, {} devteam users processed", dev_team_ckeys.size());
+
+ } catch (Exception e) {
+ logger.error("[DevRank] Error during sync", e);
+ throw new JobExecutionException(e);
+ }
+ }
+
+ private record AdminEntry(int id, String rank, int flags) {
+ }
+}
diff --git a/config.toml.example b/config.toml.example
index 94e5d48..1950360 100644
--- a/config.toml.example
+++ b/config.toml.example
@@ -1,5 +1,11 @@
# This is an example configuration. Please see README.MD
+[forumsDatabase]
+host = "172.16.0.200"
+username = "myuser"
+password = "mypassword"
+database = "paradise_forums"
+
[gameDatabase]
host = "172.16.0.200"
username = "myuser"