diff --git a/build.gradle b/build.gradle index 7333599..1f47480 100644 --- a/build.gradle +++ b/build.gradle @@ -21,12 +21,12 @@ license { repositories { mavenCentral() - maven { url 'https://nexus.lucko.me/repository/maven-snapshots/' } + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } } dependencies { compileOnly 'org.slf4j:slf4j-api:1.7.36' - compileOnly 'net.luckperms:api:5.5-20231022.200451-2' + compileOnly 'net.luckperms:api:5.5-20240218.224354-2' implementation 'io.javalin:javalin:4.6.4' implementation 'io.javalin:javalin-openapi:4.6.4' } diff --git a/src/main/java/me/lucko/luckperms/extension/rest/RestServer.java b/src/main/java/me/lucko/luckperms/extension/rest/RestServer.java index a984792..35f59c4 100644 --- a/src/main/java/me/lucko/luckperms/extension/rest/RestServer.java +++ b/src/main/java/me/lucko/luckperms/extension/rest/RestServer.java @@ -36,6 +36,7 @@ import io.javalin.plugin.json.JavalinJackson; import io.javalin.plugin.openapi.utils.OpenApiVersionUtil; import me.lucko.luckperms.extension.rest.controller.ActionController; +import me.lucko.luckperms.extension.rest.controller.EventController; import me.lucko.luckperms.extension.rest.controller.GroupController; import me.lucko.luckperms.extension.rest.controller.PermissionHolderController; import me.lucko.luckperms.extension.rest.controller.TrackController; @@ -58,6 +59,7 @@ import static io.javalin.apibuilder.ApiBuilder.path; import static io.javalin.apibuilder.ApiBuilder.post; import static io.javalin.apibuilder.ApiBuilder.put; +import static io.javalin.apibuilder.ApiBuilder.sse; /** * An HTTP server that implements a REST API for LuckPerms. @@ -67,6 +69,7 @@ public class RestServer implements AutoCloseable { private final ObjectMapper objectMapper; private final Javalin app; + private final AutoCloseable routesClosable; public RestServer(LuckPerms luckPerms, int port) { LOGGER.info("[REST] Starting server..."); @@ -78,13 +81,18 @@ public RestServer(LuckPerms luckPerms, int port) { this.setupLogging(this.app); this.setupErrorHandlers(this.app); - this.setupRoutes(this.app, luckPerms); + this.routesClosable = this.setupRoutes(this.app, luckPerms); LOGGER.info("[REST] Startup complete! Listening on http://localhost:" + port); } @Override public void close() { + try { + this.routesClosable.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } this.app.close(); } @@ -114,7 +122,7 @@ private void setupErrorHandlers(Javalin app) { }); } - private void setupRoutes(Javalin app, LuckPerms luckPerms) { + private AutoCloseable setupRoutes(Javalin app, LuckPerms luckPerms) { app.get("/", ctx -> ctx.redirect("/docs/swagger-ui")); app.get("health", ctx -> { @@ -128,22 +136,20 @@ private void setupRoutes(Javalin app, LuckPerms luckPerms) { GroupController groupController = new GroupController(luckPerms.getGroupManager(), messagingService, this.objectMapper); TrackController trackController = new TrackController(luckPerms.getTrackManager(), luckPerms.getGroupManager(), messagingService, this.objectMapper); ActionController actionController = new ActionController(luckPerms.getActionLogger()); + EventController eventController = new EventController(luckPerms.getEventBus()); app.routes(() -> { path("user", () -> { get("lookup", userController::lookup); setupControllerRoutes(userController); }); - path("group", () -> { - setupControllerRoutes(groupController); - }); - path("track", () -> { - setupControllerRoutes(trackController); - }); - path("action", () -> { - setupControllerRoutes(actionController); - }); + path("group", () -> setupControllerRoutes(groupController)); + path("track", () -> setupControllerRoutes(trackController)); + path("action", () -> setupControllerRoutes(actionController)); + path("event", () -> setupControllerRoutes(eventController)); }); + + return eventController; } private void setupControllerRoutes(PermissionHolderController controller) { @@ -192,6 +198,14 @@ private void setupControllerRoutes(ActionController controller) { post(controller::submit); } + private void setupControllerRoutes(EventController controller) { + sse("log-broadcast", controller::logBroadcast); + sse("post-network-sync", controller::postNetworkSync); + sse("post-sync", controller::postSync); + sse("pre-network-sync", controller::preNetworkSync); + sse("pre-sync", controller::preSync); + } + private void setupAuth(JavalinConfig config) { if (RestConfig.getBoolean("auth", false)) { Set keys = ImmutableSet.copyOf( @@ -237,7 +251,12 @@ private void setupAuth(JavalinConfig config) { } private void setupLogging(Javalin app) { - app.before(ctx -> ctx.attribute("startTime", System.currentTimeMillis())); + app.before(ctx -> { + ctx.attribute("startTime", System.currentTimeMillis()); + if (ctx.path().startsWith("/event/")) { + LOGGER.info("[REST] %s %s - %d".formatted(ctx.method(), ctx.path(), ctx.status())); + } + }); app.after(ctx -> { //noinspection ConstantConditions long startTime = ctx.attribute("startTime"); diff --git a/src/main/java/me/lucko/luckperms/extension/rest/bind/ActionSerializer.java b/src/main/java/me/lucko/luckperms/extension/rest/bind/ActionSerializer.java new file mode 100644 index 0000000..f966001 --- /dev/null +++ b/src/main/java/me/lucko/luckperms/extension/rest/bind/ActionSerializer.java @@ -0,0 +1,66 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.extension.rest.bind; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import net.luckperms.api.actionlog.Action; + +import java.io.IOException; +import java.util.UUID; + +public class ActionSerializer extends JsonSerializer { + + @Override + public void serialize(Action value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writePOJO(Model.from(value)); + } + + + record Model(long timestamp, SourceModel source, TargetModel target, String description) { + static Model from(Action action) { + return new Model( + action.getTimestamp().getEpochSecond(), + SourceModel.from(action.getSource()), + TargetModel.from(action.getTarget()), + action.getDescription() + ); + } + } + + record SourceModel(UUID uniqueId, String name) { + static SourceModel from(Action.Source source) { + return new SourceModel(source.getUniqueId(), source.getName()); + } + } + + record TargetModel(UUID uniqueId, String name, Action.Target.Type type) { + static TargetModel from(Action.Target action) { + return new TargetModel(action.getUniqueId().orElse(null), action.getName(), action.getType()); + } + } +} diff --git a/src/main/java/me/lucko/luckperms/extension/rest/bind/event/LogBroadcastEventSerializer.java b/src/main/java/me/lucko/luckperms/extension/rest/bind/event/LogBroadcastEventSerializer.java new file mode 100644 index 0000000..f6773d2 --- /dev/null +++ b/src/main/java/me/lucko/luckperms/extension/rest/bind/event/LogBroadcastEventSerializer.java @@ -0,0 +1,52 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.extension.rest.bind.event; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import net.luckperms.api.actionlog.Action; +import net.luckperms.api.event.log.LogBroadcastEvent; + +import java.io.IOException; + +public class LogBroadcastEventSerializer extends JsonSerializer { + + @Override + public void serialize(LogBroadcastEvent value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writePOJO(Model.from(value)); + } + + record Model(Action entry, LogBroadcastEvent.Origin origin) { + static Model from(LogBroadcastEvent event) { + return new Model( + event.getEntry(), + event.getOrigin() + ); + } + } + +} diff --git a/src/main/java/me/lucko/luckperms/extension/rest/bind/event/PostNetworkSyncEventSerializer.java b/src/main/java/me/lucko/luckperms/extension/rest/bind/event/PostNetworkSyncEventSerializer.java new file mode 100644 index 0000000..6eb038c --- /dev/null +++ b/src/main/java/me/lucko/luckperms/extension/rest/bind/event/PostNetworkSyncEventSerializer.java @@ -0,0 +1,57 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.extension.rest.bind.event; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import net.luckperms.api.actionlog.Action; +import net.luckperms.api.event.log.LogBroadcastEvent; +import net.luckperms.api.event.sync.PostNetworkSyncEvent; +import net.luckperms.api.event.sync.SyncType; + +import java.io.IOException; +import java.util.UUID; + +public class PostNetworkSyncEventSerializer extends JsonSerializer { + + @Override + public void serialize(PostNetworkSyncEvent value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writePOJO(Model.from(value)); + } + + record Model(UUID syncId, SyncType type, boolean didSyncOccur, UUID specificUserUniqueId) { + static Model from(PostNetworkSyncEvent event) { + return new Model( + event.getSyncId(), + event.getType(), + event.didSyncOccur(), + event.getSpecificUserUniqueId() + ); + } + } + +} diff --git a/src/main/java/me/lucko/luckperms/extension/rest/bind/event/PostSyncEventSerializer.java b/src/main/java/me/lucko/luckperms/extension/rest/bind/event/PostSyncEventSerializer.java new file mode 100644 index 0000000..2ff5935 --- /dev/null +++ b/src/main/java/me/lucko/luckperms/extension/rest/bind/event/PostSyncEventSerializer.java @@ -0,0 +1,50 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.extension.rest.bind.event; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import net.luckperms.api.actionlog.Action; +import net.luckperms.api.event.log.LogBroadcastEvent; +import net.luckperms.api.event.sync.PostSyncEvent; + +import java.io.IOException; + +public class PostSyncEventSerializer extends JsonSerializer { + + @Override + public void serialize(PostSyncEvent value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writePOJO(Model.from(value)); + } + + record Model() { + static Model from(PostSyncEvent event) { + return new Model(); + } + } + +} diff --git a/src/main/java/me/lucko/luckperms/extension/rest/bind/event/PreNetworkSyncEventSerializer.java b/src/main/java/me/lucko/luckperms/extension/rest/bind/event/PreNetworkSyncEventSerializer.java new file mode 100644 index 0000000..5741a63 --- /dev/null +++ b/src/main/java/me/lucko/luckperms/extension/rest/bind/event/PreNetworkSyncEventSerializer.java @@ -0,0 +1,55 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.extension.rest.bind.event; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import net.luckperms.api.event.sync.PostNetworkSyncEvent; +import net.luckperms.api.event.sync.PreNetworkSyncEvent; +import net.luckperms.api.event.sync.SyncType; + +import java.io.IOException; +import java.util.UUID; + +public class PreNetworkSyncEventSerializer extends JsonSerializer { + + @Override + public void serialize(PreNetworkSyncEvent value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writePOJO(Model.from(value)); + } + + record Model(UUID syncId, SyncType type, UUID specificUserUniqueId) { + static Model from(PreNetworkSyncEvent event) { + return new Model( + event.getSyncId(), + event.getType(), + event.getSpecificUserUniqueId() + ); + } + } + +} diff --git a/src/main/java/me/lucko/luckperms/extension/rest/bind/event/PreSyncEventSerializer.java b/src/main/java/me/lucko/luckperms/extension/rest/bind/event/PreSyncEventSerializer.java new file mode 100644 index 0000000..8bf7e21 --- /dev/null +++ b/src/main/java/me/lucko/luckperms/extension/rest/bind/event/PreSyncEventSerializer.java @@ -0,0 +1,49 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.extension.rest.bind.event; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import net.luckperms.api.event.sync.PostSyncEvent; +import net.luckperms.api.event.sync.PreSyncEvent; + +import java.io.IOException; + +public class PreSyncEventSerializer extends JsonSerializer { + + @Override + public void serialize(PreSyncEvent value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writePOJO(Model.from(value)); + } + + record Model() { + static Model from(PreSyncEvent event) { + return new Model(); + } + } + +} diff --git a/src/main/java/me/lucko/luckperms/extension/rest/controller/EventController.java b/src/main/java/me/lucko/luckperms/extension/rest/controller/EventController.java new file mode 100644 index 0000000..e50f399 --- /dev/null +++ b/src/main/java/me/lucko/luckperms/extension/rest/controller/EventController.java @@ -0,0 +1,120 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.extension.rest.controller; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import io.javalin.http.sse.SseClient; +import net.luckperms.api.event.EventBus; +import net.luckperms.api.event.EventSubscription; +import net.luckperms.api.event.LuckPermsEvent; +import net.luckperms.api.event.log.LogBroadcastEvent; +import net.luckperms.api.event.sync.PostNetworkSyncEvent; +import net.luckperms.api.event.sync.PostSyncEvent; +import net.luckperms.api.event.sync.PreNetworkSyncEvent; +import net.luckperms.api.event.sync.PreSyncEvent; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +public class EventController implements AutoCloseable { + + private final EventBus eventBus; + private final Set clients; + private final AtomicLong pingCounter; + + private final ScheduledExecutorService executor; + + public EventController(EventBus eventBus) { + this.eventBus = eventBus; + this.clients = ConcurrentHashMap.newKeySet(); + this.pingCounter = new AtomicLong(); + + this.executor = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder() + .setNameFormat("luckperms-rest-event-controller-%d") + .build()); + this.executor.scheduleAtFixedRate(this::tick, 10, 10, TimeUnit.SECONDS); + } + + public void tick() { + long pingId = this.pingCounter.incrementAndGet(); + for (SseClient client : this.clients) { + client.sendEvent("ping", pingId); + } + } + + @Override + public void close() throws Exception { + this.executor.shutdown(); + for (SseClient client : this.clients) { + client.close(); + } + } + + private void handle(SseClient client, Class eventClass) { + this.clients.add(client); + CompletableFuture future = new CompletableFuture<>(); + EventSubscription subscription = this.eventBus.subscribe(eventClass, client::sendEvent); + client.onClose(() -> { + future.complete(null); + subscription.close(); + this.clients.remove(client); + }); + client.ctx.future(future); + + } + + // GET /log-broadcast + public void logBroadcast(SseClient client) { + handle(client, LogBroadcastEvent.class); + } + + // GET /post-network-sync + public void postNetworkSync(SseClient client) { + handle(client, PostNetworkSyncEvent.class); + } + + // GET /post-sync + public void postSync(SseClient client) { + handle(client, PostSyncEvent.class); + } + + // GET /pre-network-sync + public void preNetworkSync(SseClient client) { + handle(client, PreNetworkSyncEvent.class); + } + + // GET /pre-sync + public void preSync(SseClient client) { + handle(client, PreSyncEvent.class); + } + + +} diff --git a/src/main/java/me/lucko/luckperms/extension/rest/util/CustomObjectMapper.java b/src/main/java/me/lucko/luckperms/extension/rest/util/CustomObjectMapper.java index 2a11a8b..74401c5 100644 --- a/src/main/java/me/lucko/luckperms/extension/rest/util/CustomObjectMapper.java +++ b/src/main/java/me/lucko/luckperms/extension/rest/util/CustomObjectMapper.java @@ -30,6 +30,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import me.lucko.luckperms.extension.rest.bind.ActionDeserializer; +import me.lucko.luckperms.extension.rest.bind.ActionSerializer; import me.lucko.luckperms.extension.rest.bind.ContextSetDeserializer; import me.lucko.luckperms.extension.rest.bind.ContextSetSerializer; import me.lucko.luckperms.extension.rest.bind.DemotionResultSerializer; @@ -43,9 +44,19 @@ import me.lucko.luckperms.extension.rest.bind.QueryOptionsDeserializer; import me.lucko.luckperms.extension.rest.bind.TrackSerializer; import me.lucko.luckperms.extension.rest.bind.UserSerializer; +import me.lucko.luckperms.extension.rest.bind.event.LogBroadcastEventSerializer; +import me.lucko.luckperms.extension.rest.bind.event.PostNetworkSyncEventSerializer; +import me.lucko.luckperms.extension.rest.bind.event.PostSyncEventSerializer; +import me.lucko.luckperms.extension.rest.bind.event.PreNetworkSyncEventSerializer; +import me.lucko.luckperms.extension.rest.bind.event.PreSyncEventSerializer; import net.luckperms.api.actionlog.Action; import net.luckperms.api.cacheddata.CachedMetaData; import net.luckperms.api.context.ContextSet; +import net.luckperms.api.event.log.LogBroadcastEvent; +import net.luckperms.api.event.sync.PostNetworkSyncEvent; +import net.luckperms.api.event.sync.PostSyncEvent; +import net.luckperms.api.event.sync.PreNetworkSyncEvent; +import net.luckperms.api.event.sync.PreSyncEvent; import net.luckperms.api.model.PlayerSaveResult; import net.luckperms.api.model.group.Group; import net.luckperms.api.model.user.User; @@ -66,6 +77,7 @@ public CustomObjectMapper() { SimpleModule module = new SimpleModule(); module.addDeserializer(Action.class, new ActionDeserializer()); + module.addSerializer(Action.class, new ActionSerializer()); module.addDeserializer(ContextSet.class, new ContextSetDeserializer()); module.addSerializer(ContextSet.class, new ContextSetSerializer()); module.addSerializer(DemotionResult.class, new DemotionResultSerializer()); @@ -79,6 +91,13 @@ public CustomObjectMapper() { module.addDeserializer(QueryOptions.class, new QueryOptionsDeserializer()); module.addSerializer(Track.class, new TrackSerializer()); module.addSerializer(User.class, new UserSerializer()); + + module.addSerializer(LogBroadcastEvent.class, new LogBroadcastEventSerializer()); + module.addSerializer(PostNetworkSyncEvent.class, new PostNetworkSyncEventSerializer()); + module.addSerializer(PostSyncEvent.class, new PostSyncEventSerializer()); + module.addSerializer(PreNetworkSyncEvent.class, new PreNetworkSyncEventSerializer()); + module.addSerializer(PreSyncEvent.class, new PreSyncEventSerializer()); + this.registerModule(module); } diff --git a/src/main/resources/luckperms-openapi.yml b/src/main/resources/luckperms-openapi.yml index 23283c0..d8d9c0c 100644 --- a/src/main/resources/luckperms-openapi.yml +++ b/src/main/resources/luckperms-openapi.yml @@ -18,6 +18,8 @@ tags: description: API methods for LuckPerms groups. - name: Tracks description: API methods for LuckPerms tracks. + - name: Events + description: API methods for LuckPerms events. - name: Actions description: API methods for LuckPerms actions. - name: Misc @@ -1260,6 +1262,161 @@ paths: description: Submit a new action to the action logger. tags: - Actions + /event/log-broadcast: + get: + summary: Subscribe to the LogBroadcastEvent + operationId: get-event-log-broadcast + responses: + '200': + description: Ok + content: + text/event-stream: + schema: + type: array + format: event-stream + items: + type: object + properties: + event: + type: string + enum: [message] + data: + $ref: '#/components/schemas/LogBroadcastEvent' + required: + - event + - data + examples: + example-1: + value: | + event: message + data: '{"entry":{"timestamp":1658428395,"source":{"uniqueId":"c1d60c50-70b5-4722-8057-87767557e50d","name":"Luck"},"target":{"uniqueId":"c1d60c50-70b5-4722-8057-87767557e50d","name":"Luck","type":"user"},"description":"permission set minecraft.command.ban true"},"origin":"local"}' + description: Subscribes to the LogBroadcastEvent using [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) + tags: + - Events + /event/post-network-sync: + get: + summary: Subscribe to the PostNetworkSyncEvent + operationId: get-event-post-network-sync + responses: + '200': + description: Ok + content: + text/event-stream: + schema: + type: array + format: event-stream + items: + type: object + properties: + event: + type: string + enum: [message] + data: + $ref: '#/components/schemas/PostNetworkSyncEvent' + required: + - event + - data + examples: + example-1: + value: | + event: message + data: '{"syncId":"8ff071c2-f772-4c16-a5d2-17a401d4e2f1","type":"specific_user","didSyncOccur":true,"specificUserUniqueId":"c1d60c50-70b5-4722-8057-87767557e50d"}' + description: Subscribes to the PostNetworkSyncEvent using [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) + tags: + - Events + /event/post-sync: + get: + summary: Subscribe to the PostSyncEvent + operationId: get-event-post-sync + responses: + '200': + description: Ok + content: + text/event-stream: + schema: + type: array + format: event-stream + items: + type: object + properties: + event: + type: string + enum: [message] + data: + $ref: '#/components/schemas/PostSyncEvent' + required: + - event + - data + examples: + example-1: + value: | + event: message + data: '{}' + description: Subscribes to the PostSyncEvent using [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) + tags: + - Events + /event/pre-network-sync: + get: + summary: Subscribe to the PreNetworkSyncEvent + operationId: get-event-pre-network-sync + responses: + '200': + description: Ok + content: + text/event-stream: + schema: + type: array + format: event-stream + items: + type: object + properties: + event: + type: string + enum: [message] + data: + $ref: '#/components/schemas/PreNetworkSyncEvent' + required: + - event + - data + examples: + example-1: + value: | + event: message + data: '{"syncId":"8ff071c2-f772-4c16-a5d2-17a401d4e2f1","type":"specific_user","specificUserUniqueId":"c1d60c50-70b5-4722-8057-87767557e50d"}' + description: Subscribes to the PreNetworkSyncEvent using [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) + tags: + - Events + /event/pre-sync: + get: + summary: Subscribe to the PreSyncEvent + operationId: get-event-pre-sync + responses: + '200': + description: Ok + content: + text/event-stream: + schema: + type: array + format: event-stream + items: + type: object + properties: + event: + type: string + enum: [message] + data: + $ref: '#/components/schemas/PreSyncEvent' + required: + - event + - data + examples: + example-1: + value: | + event: message + data: '{}' + description: Subscribes to the PreSyncEvent using [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) + tags: + - Events /health: get: summary: Get the current health status of the app @@ -1813,6 +1970,102 @@ components: - username_updated - other_unique_ids_present_for_username description: The statuses returned in a PlayerSaveResult + LogBroadcastEvent: + title: LogBroadcastEvent + type: object + description: Called when a log entry is about to be sent to notifiable players on the platform + properties: + entry: + $ref: '#/components/schemas/Action' + origin: + $ref: '#/components/schemas/LogBroadcastEventOrigin' + required: + - entry + - origin + x-examples: + example-1: + entry: + timestamp: 1658428395 + source: + uniqueId: c1d60c50-70b5-4722-8057-87767557e50d + name: Luck + target: + uniqueId: c1d60c50-70b5-4722-8057-87767557e50d + name: Luck + type: user + description: permission set minecraft.command.ban true + origin: local + LogBroadcastEventOrigin: + title: LogBroadcastEventOrigin + type: string + enum: + - local + - local_api + - remote + description: Represents where a log entry is from + SyncType: + title: SyncType + type: string + description: Represents the type of synchronisation task + enum: + - full + - specific_user + PostNetworkSyncEvent: + title: PostNetworkSyncEvent + type: object + description: Called after a network synchronisation task has been completed + required: + - syncId + - type + - didSyncOccur + properties: + syncId: + $ref: '#/components/schemas/UniqueId' + type: + $ref: '#/components/schemas/SyncType' + didSyncOccur: + type: boolean + specificUserUniqueId: + $ref: '#/components/schemas/UniqueId' + x-examples: + example-1: + syncId: 8ff071c2-f772-4c16-a5d2-17a401d4e2f1 + type: specific_user + didSyncOccur: true + specificUserUniqueId: c1d60c50-70b5-4722-8057-87767557e50d + PostSyncEvent: + title: PostSyncEvent + type: object + description: Called after a full synchronisation task has been completed + properties: {} + x-examples: + example-1: {} + PreNetworkSyncEvent: + title: PreNetworkSyncEvent + type: object + description: Called after a request for synchronisation has been received via the messaging service, but before it has actually been completed + required: + - syncId + - type + properties: + syncId: + $ref: '#/components/schemas/UniqueId' + type: + $ref: '#/components/schemas/SyncType' + specificUserUniqueId: + $ref: '#/components/schemas/UniqueId' + x-examples: + example-1: + syncId: 8ff071c2-f772-4c16-a5d2-17a401d4e2f1 + type: specific_user + specificUserUniqueId: c1d60c50-70b5-4722-8057-87767557e50d + PreSyncEvent: + title: PreSyncEvent + type: object + description: Called just before a full synchronisation task runs + properties: {} + x-examples: + example-1: {} securitySchemes: apikey: type: http