This commit is contained in:
kento2 2026-05-31 10:49:33 +02:00
parent 0b0b650f83
commit e4fd6a8c5f
12 changed files with 236 additions and 46 deletions

View file

@ -8,20 +8,23 @@ docker run -d --network scrow -e ME_CONFIG_MONGODB_SERVER=mongo -e ME_CONFIG_MON
docker run -d --network scrow -p 4222:4222 --name nats nats docker run -d --network scrow -p 4222:4222 --name nats nats
``` ```
acce**ss the mongo-db web interface at http://localhost:8081; username `a`, password `1234`** access the mongo-db web interface at http://localhost:8081; username `a`, password `1234`**
modules ## ENV
-------
* APIs - HOST_NATS e.g `nats://127.17.0.1:4222`
---- - HOST_MONGO e.g `mongodb://localhost:27017/scrow`
## modules
### APIs
* core-api contains api for all java development * core-api contains api for all java development
* core-bukkit-api contains api for bukkit development * core-bukkit-api contains api for bukkit development
* depends on core-api * depends on core-api
* implementations ### implementations
---------------
the `-impl` suffix indicates that this module implements the API with the same prefix. the `-impl` suffix indicates that this module implements the API with the same prefix.
So `core-impl` implements `core-api`. So `core-impl` implements `core-api`.
* core-bukkit-impl is a spigot plugin. It shades core-impl. * core-bukkit-impl is a spigot plugin. It shades core-impl.

View file

@ -39,9 +39,9 @@ dependencies {
implementation("io.nats:jnats:2.25.2") implementation("io.nats:jnats:2.25.2")
api("io.nats:jnats:2.25.2") api("io.nats:jnats:2.25.2")
api("de.kentoj.scrow:kencommandapi-core:0.2") api("de.kentoj.scrow:kencommandapi-core:0.4")
implementation("de.kentoj.scrow:kencommandapi-bukkit:0.2") implementation("de.kentoj.scrow:kencommandapi-bukkit:0.4")
api("de.kentoj.scrow:kencommandapi-bukkit:0.2") api("de.kentoj.scrow:kencommandapi-bukkit:0.4")
} }
publishing { publishing {

View file

@ -4,7 +4,6 @@ import com.mongodb.reactivestreams.client.MongoDatabase;
import de.kentoj.kencommandapi.BukkitKenCommandApi; import de.kentoj.kencommandapi.BukkitKenCommandApi;
import de.kentoj.scrow.bukkit.friends.FriendRequestService; import de.kentoj.scrow.bukkit.friends.FriendRequestService;
import de.kentoj.scrow.bukkit.friends.FriendshipService; import de.kentoj.scrow.bukkit.friends.FriendshipService;
import de.kentoj.scrow.bukkit.message.NatsDocumentRepository;
import io.nats.client.Connection; import io.nats.client.Connection;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
@ -31,7 +30,7 @@ public class ScrowAPI {
@Getter @Getter
private static Connection nats; private static Connection nats;
@Getter @Getter
private static NatsDocumentRepository natsRepository; private static ServerManager serverManager;
@ApiStatus.Internal @ApiStatus.Internal
public static void setDatabase(@Nullable MongoDatabase database) { public static void setDatabase(@Nullable MongoDatabase database) {
@ -69,8 +68,8 @@ public class ScrowAPI {
} }
@ApiStatus.Internal @ApiStatus.Internal
public static void setNatsRepository(NatsDocumentRepository natsRepository) { public static void setServerManager(ServerManager serverManager) {
ScrowAPI.natsRepository = natsRepository; ScrowAPI.serverManager = serverManager;
} }
} }

View file

@ -0,0 +1,25 @@
package de.kentoj.scrow.bukkit;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ServerManager {
/**
* @return Mono with handle of deployed server
*/
Mono<@NotNull String> deployServer(String template);
/**
* @return Flux with handles of running servers
*/
Flux<@NotNull String> getRunningServers();
/**
* Attempts a clean shutdown, if that doesn't succeed, kills the server.
*/
Mono<@NotNull Void> destroyServer(String handle);
Mono<@NotNull Void> updateState(String handle, String state);
}

View file

@ -1,10 +1,8 @@
package de.kentoj.scrow.bukkit; package de.kentoj.scrow.bukkit;
import de.kentoj.scrow.bukkit.crown.command.CrownCommand;
import de.kentoj.scrow.bukkit.economy.CoinsCommand; import de.kentoj.scrow.bukkit.economy.CoinsCommand;
import de.kentoj.scrow.bukkit.friends.command.FriendCommand; import de.kentoj.scrow.bukkit.friends.command.FriendCommand;
import io.nats.client.Message;
import io.nats.client.Nats;
import org.bson.Document;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -21,33 +19,14 @@ public class CoreImplPlugin extends JavaPlugin {
{ {
ScrowAPI.getCommandManager().register(new FriendCommand().getRootNode()); ScrowAPI.getCommandManager().register(new FriendCommand().getRootNode());
ScrowAPI.getCommandManager().register(new CoinsCommand().getRootNode()); ScrowAPI.getCommandManager().register(new CoinsCommand().getRootNode());
ScrowAPI.getCommandManager().register(new CrownCommand().getRootNode());
} }
{ registerServer();
var playerId = UUID.randomUUID();
var economyService = ScrowAPI.getEconomyService();
Mono.when(
economyService.setCoins(playerId, 120),
economyService.depositCoins(playerId, 100)
)
.then(economyService.tryWithdrawCoins(playerId, 300))
.doOnNext(a -> System.out.println("withdraw 1 success " + a))
.then(economyService.tryWithdrawCoins(playerId, 50))
.doOnNext(a -> System.out.println("withdraw 2 success " + a))
.then(economyService.getCoins(playerId))
.doOnNext(a -> System.out.println("coins: " + a))
.block();
}
} }
private void registerServer() { private void registerServer() {
ScrowAPI.getNatsRepository().publish("crown.status", new Document() ScrowAPI.getServerManager().updateState(System.getenv("SERVER_HANDLE"), "UP");
.append("state", "up"));
ScrowAPI.getNatsRepository().subscribe("crown.status", doc -> {
doc.
});
ScrowAPI.getNats().reqpublish("crown.status", new Document()
.append("state", "up").toJson());
} }
@Override @Override

View file

@ -5,6 +5,7 @@ import com.mongodb.MongoClientSettings;
import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients; import com.mongodb.reactivestreams.client.MongoClients;
import de.kentoj.kencommandapi.BukkitKenCommandApi; import de.kentoj.kencommandapi.BukkitKenCommandApi;
import de.kentoj.scrow.bukkit.crown.CrownServerManager;
import de.kentoj.scrow.bukkit.economy.InMemoryEconomyService; import de.kentoj.scrow.bukkit.economy.InMemoryEconomyService;
import de.kentoj.scrow.bukkit.economy.MongoEconomyService; import de.kentoj.scrow.bukkit.economy.MongoEconomyService;
import de.kentoj.scrow.bukkit.friends.friendship.InMemoryFriendshipService; import de.kentoj.scrow.bukkit.friends.friendship.InMemoryFriendshipService;
@ -12,6 +13,7 @@ import de.kentoj.scrow.bukkit.friends.friendship.MongoFriendshipService;
import de.kentoj.scrow.bukkit.friends.request.InMemoryFriendRequestService; import de.kentoj.scrow.bukkit.friends.request.InMemoryFriendRequestService;
import de.kentoj.scrow.bukkit.friends.request.MongoFriendRequestService; import de.kentoj.scrow.bukkit.friends.request.MongoFriendRequestService;
import io.nats.client.Nats; import io.nats.client.Nats;
import io.nats.client.Options;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.bson.UuidRepresentation; import org.bson.UuidRepresentation;
@ -35,7 +37,12 @@ public class ScrowAPISurface {
})); }));
try { try {
ScrowAPI.setMessageBroker(Nats.connect()); String natsHost = System.getenv("HOST_NATS");
var options = Options.builder()
.server(natsHost)
.pedantic()
.build();
ScrowAPI.setNats(Nats.connect(options));
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -46,18 +53,21 @@ public class ScrowAPISurface {
MongoClientSettings.getDefaultCodecRegistry(), MongoClientSettings.getDefaultCodecRegistry(),
CodecRegistries.fromProviders(pojoCodecProvider) CodecRegistries.fromProviders(pojoCodecProvider)
); );
String mongoHost = System.getenv("HOST_MONGO");
var settings = MongoClientSettings.builder() var settings = MongoClientSettings.builder()
.codecRegistry(codecRegistry) .codecRegistry(codecRegistry)
.uuidRepresentation(UuidRepresentation.STANDARD) .uuidRepresentation(UuidRepresentation.STANDARD)
.applyConnectionString(new ConnectionString("mongodb://localhost:27017/scrow")) .applyConnectionString(new ConnectionString(mongoHost))
.build(); .build();
mongoClient = MongoClients.create(settings); mongoClient = MongoClients.create(settings);
ScrowAPI.setDatabase(mongoClient.getDatabase("scrow")); ScrowAPI.setDatabase(mongoClient.getDatabase("scrow"));
} }
ScrowAPI.setServerManager(new CrownServerManager(ScrowAPI.getNats()));
ScrowAPI.setEconomyService(ScrowAPI.getDatabase() != null ? ScrowAPI.setEconomyService(ScrowAPI.getDatabase() != null ?
new MongoEconomyService(ScrowAPI.getDatabase()) : new InMemoryEconomyService()); new MongoEconomyService(ScrowAPI.getDatabase()) : new InMemoryEconomyService());
ScrowAPI.setFriendRequestService(ScrowAPI.getDatabase() != null ? ScrowAPI.setFriendRequestService(ScrowAPI.getDatabase() != null ?
new MongoFriendRequestService(ScrowAPI.getDatabase()) : new InMemoryFriendRequestService()); new MongoFriendRequestService(ScrowAPI.getDatabase()) : new InMemoryFriendRequestService());

View file

@ -0,0 +1,47 @@
package de.kentoj.scrow.bukkit.crown;
import com.google.common.base.Preconditions;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.ServerManager;
import io.nats.client.Connection;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Arrays;
@RequiredArgsConstructor
public class CrownServerManager implements ServerManager {
private final Connection nats;
@Override
public Mono<@NotNull String> deployServer(String template) {
return Mono.fromFuture(nats.request("crown.deploy-server", template.getBytes()))
.map(msg -> new String(msg.getData()));
}
@Override
public Flux<@NotNull String> getRunningServers() {
return Mono.fromFuture(nats.request("crown.deploy-server", new byte[0]))
.flatMapMany(msg -> {
var ids = new String(msg.getData()).split("\u001F");
return Flux.fromStream(Arrays.stream(ids));
});
}
@Override
public Mono<@NotNull Void> destroyServer(String handle) {
return Mono.fromFuture(nats.request("crown.destroy-server",handle.getBytes()))
.then();
}
@Override
public Mono<@NotNull Void> updateState(String handle, String state) {
Preconditions.checkArgument(!state.contains(" "), "state may not contain spaces");
var payload = (handle + " " + state).getBytes();
return Mono.fromFuture(nats.request("crown.update-state", payload))
.then();
}
}

View file

@ -0,0 +1,20 @@
package de.kentoj.scrow.bukkit.crown.command;
import de.kentoj.kencommandapi.BukkitCommandContext;
import de.kentoj.kencommandapi.api.structure.node.CommandNode;
import de.kentoj.scrow.bukkit.ScrowAPI;
import lombok.Getter;
import org.bukkit.command.CommandSender;
public class CrownCommand {
@Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode;
public CrownCommand() {
this.rootNode = ScrowAPI.getCommandManager().createNode("crown", "server-manager");
this.rootNode.addLiteral(new CrownListServersLiteral().getNode());
this.rootNode.addLiteral(new CrownDeployServerLiteral().getNode());
this.rootNode.addLiteral(new CrownDestroyServerLiteral().getNode());
}
}

View file

@ -0,0 +1,35 @@
package de.kentoj.scrow.bukkit.crown.command;
import de.kentoj.kencommandapi.BukkitCommandContext;
import de.kentoj.kencommandapi.api.processing.CommandExecutor;
import de.kentoj.kencommandapi.api.structure.argument.CommandArgument;
import de.kentoj.kencommandapi.api.structure.argument.types.StringArgumentType;
import de.kentoj.kencommandapi.api.structure.node.CommandNode;
import de.kentoj.scrow.bukkit.ScrowAPI;
import lombok.Getter;
import org.bukkit.command.CommandSender;
public class CrownDeployServerLiteral implements CommandExecutor<CommandSender, BukkitCommandContext> {
@Getter
private final CommandNode<CommandSender, BukkitCommandContext> node;
private final CommandArgument<CommandSender, BukkitCommandContext, String> templateArg;
public CrownDeployServerLiteral() {
this.node = ScrowAPI.getCommandManager().createNode("deploy-server");
this.node.setExecutor(this);
{
this.templateArg = ScrowAPI.getCommandManager().createArgument("template", new StringArgumentType<>());
this.node.addArgument(templateArg);
}
}
@Override
public void execute(BukkitCommandContext ctx) {
var template = ctx.getArg(templateArg);
ScrowAPI.getServerManager().deployServer(template)
.subscribe(handle -> {
ctx.getSender().sendMessage("Deployed instance of " + template + " with handle " + handle);
});
}
}

View file

@ -0,0 +1,41 @@
package de.kentoj.scrow.bukkit.crown.command;
import de.kentoj.kencommandapi.BukkitCommandContext;
import de.kentoj.kencommandapi.api.processing.CommandExecutor;
import de.kentoj.kencommandapi.api.structure.argument.CommandArgument;
import de.kentoj.kencommandapi.api.structure.argument.types.StringArgumentType;
import de.kentoj.kencommandapi.api.structure.node.CommandNode;
import de.kentoj.scrow.bukkit.ScrowAPI;
import lombok.Getter;
import org.bukkit.command.CommandSender;
import java.util.stream.Collectors;
public class CrownDestroyServerLiteral implements CommandExecutor<CommandSender, BukkitCommandContext> {
@Getter
private final CommandNode<CommandSender, BukkitCommandContext> node;
private final CommandArgument<CommandSender, BukkitCommandContext, String> handleArg;
public CrownDestroyServerLiteral() {
this.node = ScrowAPI.getCommandManager().createNode("destroy-server");
this.node.setExecutor(this);
{
this.handleArg = ScrowAPI.getCommandManager().createArgument("template", new StringArgumentType<>());
this.handleArg.setSuggestionProvider(ctx -> {
//noinspection CodeBlock2Expr
return ScrowAPI.getServerManager().getRunningServers().collect(Collectors.toSet()).toFuture();
});
this.node.addArgument(handleArg);
}
}
@Override
public void execute(BukkitCommandContext ctx) {
var handle = ctx.getArg(handleArg);
ScrowAPI.getServerManager().destroyServer(handle)
.subscribe(__ -> {
ctx.getSender().sendMessage("Destroyed server with handle " + handle);
});
}
}

View file

@ -0,0 +1,31 @@
package de.kentoj.scrow.bukkit.crown.command;
import de.kentoj.kencommandapi.BukkitCommandContext;
import de.kentoj.kencommandapi.api.processing.CommandExecutor;
import de.kentoj.kencommandapi.api.structure.node.CommandNode;
import de.kentoj.scrow.bukkit.ScrowAPI;
import lombok.Getter;
import org.bukkit.command.CommandSender;
public class CrownListServersLiteral implements CommandExecutor<CommandSender, BukkitCommandContext> {
@Getter
private final CommandNode<CommandSender, BukkitCommandContext> node;
public CrownListServersLiteral() {
this.node = ScrowAPI.getCommandManager().createNode("list-servers");
this.node.setExecutor(this);
}
@Override
public void execute(BukkitCommandContext ctx) {
ScrowAPI.getServerManager().getRunningServers()
.collectList()
.subscribe(uids -> {
var msg = new StringBuilder().append('\n');
uids.forEach(uid -> msg.append(" > ").append(uid));
msg.append('\n');
ctx.getPlayerSender().sendMessage(msg.toString());
});
}
}

View file

@ -23,7 +23,7 @@ public class CoinsCommand {
private final CommandArgument<CommandSender, BukkitCommandContext, Integer> amountArg; private final CommandArgument<CommandSender, BukkitCommandContext, Integer> amountArg;
public CoinsCommand() { public CoinsCommand() {
this.rootNode = ScrowAPI.getCommandManager().createNode("coins"); this.rootNode = ScrowAPI.getCommandManager().createNode("coins", "eco");
this.playerArg = ScrowAPI.getCommandManager().createArgument("player", OfflinePlayerArgumentType.getInstance()); this.playerArg = ScrowAPI.getCommandManager().createArgument("player", OfflinePlayerArgumentType.getInstance());
this.amountArg = ScrowAPI.getCommandManager().createArgument("amount", new IntegerArgumentType<>()); this.amountArg = ScrowAPI.getCommandManager().createArgument("amount", new IntegerArgumentType<>());