Skip to content

Commit

Permalink
feature(wip): postgres action listener
Browse files Browse the repository at this point in the history
  • Loading branch information
broccolai committed Apr 18, 2024
1 parent 13e15d0 commit 1692dec
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 6 deletions.
1 change: 1 addition & 0 deletions api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ plugins {

dependencies {
api(libs.configurate)
compileOnly(libs.bundles.database)
compileOnly("com.fasterxml.jackson.core", "jackson-annotations", "2.16.1")
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package love.broccolai.tickets.api.service;

import com.impossibl.postgres.api.jdbc.PGNotificationListener;
import java.time.Instant;
import java.util.Collection;
import java.util.Map;
Expand All @@ -16,6 +17,8 @@
@NullMarked
public interface StorageService {

void addNotificationListener(PGNotificationListener listener);

Ticket createTicket(TicketType type, UUID creator, String message);

void saveAction(Ticket ticket, Action action);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static class H2 {

@ConfigSerializable
public static class Postgres {
public String url = "jdbc:postgresql://localhost:5432/database";
public String url = "jdbc:pgsql://localhost:5432/database";

public String username = "username";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.gson2.Gson2Config;
import org.jdbi.v3.gson2.Gson2Plugin;
import org.jdbi.v3.postgres.PostgresPlugin;
import org.jspecify.annotations.NullMarked;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.hocon.HoconConfigurationLoader;
Expand Down Expand Up @@ -59,7 +58,7 @@ public DataSource provideDataSource(
hikariConfig.setJdbcUrl(configuration.h2.url);
}
case POSTGRES -> {
hikariConfig.setDriverClassName("org.postgresql.Driver");
hikariConfig.setDriverClassName("com.impossibl.postgres.jdbc.PGDriver");
hikariConfig.setJdbcUrl(configuration.postgres.url);
hikariConfig.setUsername(configuration.postgres.username);
hikariConfig.setPassword(configuration.postgres.password);
Expand All @@ -81,7 +80,6 @@ public Jdbi provideJdbi(
) {
Jdbi jdbi = Jdbi.create(dataSource)
.installPlugin(new Gson2Plugin())
.installPlugin(new PostgresPlugin())
.registerRowMapper(actionMapper)
.registerRowMapper(ticketMapper)
.registerColumnMapper(ticketTypeMapper)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import com.google.common.primitives.Ints;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.impossibl.postgres.api.jdbc.PGConnection;
import com.impossibl.postgres.api.jdbc.PGNotificationListener;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.Instant;
import java.util.Collection;
import java.util.LinkedHashSet;
Expand All @@ -25,6 +29,7 @@
import love.broccolai.tickets.common.serialization.jdbi.TicketAccumulator;
import love.broccolai.tickets.common.utilities.QueriesLocator;
import love.broccolai.tickets.common.utilities.TimeUtilities;
import org.jdbi.v3.core.Handle;
import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.core.qualifier.QualifiedType;
import org.jdbi.v3.core.statement.Update;
Expand Down Expand Up @@ -55,6 +60,25 @@ public DatabaseStorageService(
this.locator = new QueriesLocator(configuration.type);
}

//todo: implement listen / channel via statements.
@Override
public void addNotificationListener(final PGNotificationListener listener) {
this.jdbi.useHandle(handle -> {
PGConnection connection = this.connectionFromHandle(handle);
logger.trace("Adding notification listener: {}", listener.getClass().getSimpleName());

connection.addNotificationListener(listener);
});
}

private PGConnection connectionFromHandle(final Handle handle) {
try (Connection conn = handle.getConnection()) {
return conn.unwrap(PGConnection.class);
} catch (SQLException e) {
throw new RuntimeException("Error obtaining PGConnection", e);
}
}

@Override
public Ticket createTicket(final TicketType type, final UUID creator, final String message) {
Instant timestamp = TimeUtilities.nowTruncated();
Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ hikari = "5.1.0"
flyway = "10.11.0"
h2 = "2.2.224"
configurate = "4.1.2"
postgresql = "42.7.3"
postgresql = "0.8.9"

# Misc
corn = "4.0.0-SNAPSHOT"
Expand Down Expand Up @@ -79,7 +79,7 @@ flyway = { group = "org.flywaydb", name = "flyway-core", version.ref = "flyway"
flyway-database-postgresql = { group = "org.flywaydb", name = "flyway-database-postgresql", version.ref = "flyway" }
h2 = { group = "com.h2database", name = "h2", version.ref = "h2" }
configurate = { group = "org.spongepowered", name = "configurate-hocon", version.ref = "configurate" }
postgresql = { group = "org.postgresql", name = "postgresql", version.ref = "postgresql" }
postgresql = { group = "com.impossibl.pgjdbc-ng", name = "pgjdbc-ng", version.ref = "postgresql" }
gson = { group = "com.google.code.gson", name = "gson", version = "2.10.1" }

# Misc
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package love.broccolai.tickets.minecraft.common.command;

import com.google.inject.Inject;
import java.time.Duration;
import love.broccolai.tickets.api.service.StatisticService;
import love.broccolai.tickets.minecraft.common.model.Commander;
import love.broccolai.tickets.minecraft.common.utilities.DurationFormatter;
import org.incendo.cloud.Command;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.key.CloudKey;
import org.incendo.cloud.parser.standard.DurationParser;
import org.jspecify.annotations.NullMarked;

import static net.kyori.adventure.text.Component.text;

@NullMarked
public final class AdminCommands extends AbstractCommand {

private final static CloudKey<Duration> DURATION_KEY = CloudKey.cloudKey("duration", Duration.class);
private final static Duration FOREVER_DURATION = Duration.ofSeconds(Long.MAX_VALUE);

private final StatisticService statisticService;

@Inject
public AdminCommands(final StatisticService statisticService) {
this.statisticService = statisticService;
}

@Override
public void register(final CommandManager<Commander> commandManager) {
Command.Builder<Commander> root = commandManager
.commandBuilder("ticketsadmin");

commandManager.command(
root.literal("stats")
.literal("lifespan")
.optional("duration", DurationParser.durationParser())
.handler(this::handleLifespan)
);
}

public void handleLifespan(final CommandContext<Commander> context) {
Commander commander = context.sender();
Duration search = context.optional(DURATION_KEY)
.orElse(FOREVER_DURATION);

Duration result = this.statisticService.averageTicketsLifespan(search);
String formattedResult = DurationFormatter.formatDuration(result);

commander.sendMessage(
text("average ticket lifespan: " + formattedResult)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package love.broccolai.tickets.minecraft.common.listener;

import com.impossibl.postgres.api.jdbc.PGNotificationListener;

public final class ActionListener implements PGNotificationListener {

@Override
public void notification(int processId, String channelName, String payload) {
System.out.println(payload);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package love.broccolai.tickets.minecraft.common.utilities;

import java.time.Duration;

public final class DurationFormatter {

private DurationFormatter() {
}

/**
* Formats a Duration object into a string of format "Xh Ym".
*
* @param duration The Duration object to format.
* @return A formatted string representing the duration in hours and minutes.
*/
public static String formatDuration(final Duration duration) {
if (duration.isZero()) {
return "unknown";
}

long hours = duration.toHours();
long minutes = duration.toMinutesPart();

StringBuilder sb = new StringBuilder();
if (hours > 0) {
sb.append(hours).append("h ");
}
if (minutes > 0) {
sb.append(minutes).append("m");
}
return sb.toString().trim();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import love.broccolai.tickets.api.service.StorageService;
import love.broccolai.tickets.common.TicketsPackage;
import love.broccolai.tickets.common.configuration.DatabaseConfiguration;
import love.broccolai.tickets.minecraft.common.command.AdminCommands;
import love.broccolai.tickets.minecraft.common.command.StaffCommands;
import love.broccolai.tickets.minecraft.common.command.UserCommands;
import love.broccolai.tickets.minecraft.common.inject.CommandArgumentModule;
import love.broccolai.tickets.minecraft.common.listener.ActionListener;
import love.broccolai.tickets.minecraft.common.model.Commander;
import love.broccolai.tickets.minecraft.paper.inject.PaperPlatformModule;
import org.bukkit.plugin.java.JavaPlugin;
Expand All @@ -33,5 +37,23 @@ public void onEnable() {

injector.getInstance(UserCommands.class).register(commandManager);
injector.getInstance(StaffCommands.class).register(commandManager);
injector.getInstance(AdminCommands.class).register(commandManager);

this.setupNotifications(injector);
}

//todo: add non-notif alternative for H2.
public void setupNotifications(final Injector injector) {
DatabaseConfiguration databaseConfiguration = injector.getInstance(DatabaseConfiguration.class);

if (databaseConfiguration.type != DatabaseConfiguration.Type.POSTGRES) {
return;
}

StorageService storageService = injector.getInstance(StorageService.class);

storageService.addNotificationListener(
injector.getInstance(ActionListener.class)
);
}
}

0 comments on commit 1692dec

Please sign in to comment.