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 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 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"