This commit is contained in:
kento2 2026-02-14 20:19:23 +01:00
parent 07fa0efab7
commit 145a3116bc
29 changed files with 721 additions and 62 deletions

View file

@ -1,6 +1,17 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ClassCanBeRecord" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="myIgnoredAnnotations">
<list>
<option value="io.micronaut.*" />
<option value="jakarta.*" />
<option value="javax.*" />
<option value="lombok.Value" />
<option value="org.springframework.*" />
</list>
</option>
</inspection_tool>
<inspection_tool class="NullableProblems" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES">
<option name="REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL" value="true" />
<option name="REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL" value="true" />

View file

@ -0,0 +1,29 @@
package de.kentoj.scrow;
import com.mongodb.reactivestreams.client.MongoDatabase;
import de.kentoj.scrow.services.EconomyService;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ScrowAPI {
@Getter
private static @Nullable MongoDatabase database;
@Getter
private static EconomyService economyService;
@ApiStatus.Internal
public static void setDatabase(@Nullable MongoDatabase database) {
ScrowAPI.database = database;
}
@ApiStatus.Internal
public static void setEconomyService(EconomyService economyService) {
ScrowAPI.economyService = economyService;
}
}

View file

@ -0,0 +1,7 @@
package de.kentoj.scrow.provider;
public class MissingServiceFieldException extends RuntimeException {
public MissingServiceFieldException(Class<?> apiClass, Class<?> serviceClass) {
super("missing static field of type " + serviceClass + " annotated with " + ServiceField.class.getCanonicalName() + " in " + apiClass.getCanonicalName());
}
}

View file

@ -0,0 +1,15 @@
package de.kentoj.scrow.provider;
import de.kentoj.scrow.provider.provider.ServiceProvider;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Set;
@Getter
@AllArgsConstructor
public class RegisteredService<T> {
private final Class<T> serviceClass;
private final ServiceProvider<T> serviceProvider;
private final Set<Class<?>> requiredServices;
}

View file

@ -0,0 +1,10 @@
package de.kentoj.scrow.provider;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceField {}

View file

@ -0,0 +1,68 @@
package de.kentoj.scrow.provider;
import de.kentoj.scrow.provider.provider.ServiceProvider;
import lombok.RequiredArgsConstructor;
import java.lang.reflect.AccessFlag;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@SuppressWarnings("unchecked")
@RequiredArgsConstructor
public class ServiceRegistry {
private final Set<RegisteredService<?>> registeredServices = new HashSet<>();
public <T> void provideService(
Class<T> serviceInterfaceClass,
ServiceProvider<T> serviceProvider,
Set<Class<?>> requiredServices
) {
registeredServices.add(new RegisteredService<>(serviceInterfaceClass, serviceProvider, requiredServices));
}
public void load(Class<?> apiClass) {
Arrays.stream(apiClass.getDeclaredFields())
.filter(f -> f.accessFlags().contains(AccessFlag.STATIC))
.filter(f -> f.isAnnotationPresent(ServiceField.class))
.filter(f -> {
try {
f.setAccessible(true);
return f.get(null) == null;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
})
.forEach(field -> loadServiceRecursively(apiClass, getService(field.getType())));
}
public void loadServiceRecursively(Class<?> apiClass, RegisteredService<?> registeredService) {
for (Class<?> serviceClass : registeredService.getRequiredServices()) {
var sv = getService(serviceClass);
loadServiceRecursively(apiClass, sv);
}
var field = Arrays.stream(apiClass.getDeclaredFields())
.filter(f -> f.accessFlags().contains(AccessFlag.STATIC))
.filter(f -> f.isAnnotationPresent(ServiceField.class))
.findFirst()
.orElseThrow(() -> new MissingServiceFieldException(apiClass, registeredService.getServiceClass()));
try {
if (field.get(null) != null) return;
field.setAccessible(true);
field.set(null, registeredService.getServiceProvider().get());
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public <T> RegisteredService<T> getService(Class<T> serviceClass) {
return (RegisteredService<T>) registeredServices.stream()
.filter(Objects::nonNull)
.filter(sv -> sv.getServiceClass() == serviceClass)
.findFirst()
.orElseThrow(() -> new IllegalStateException("No registered-service for " + serviceClass.getCanonicalName() + " registered"));
}
}

View file

@ -0,0 +1,15 @@
package de.kentoj.scrow.provider.provider;
import org.jetbrains.annotations.Nullable;
public abstract class AbstractServiceProvider<T> implements ServiceProvider<T> {
private final @Nullable T service = load();
abstract protected T load();
@Override
public T get() {
return service;
}
}

View file

@ -0,0 +1,21 @@
package de.kentoj.scrow.provider.provider;
import java.util.function.Supplier;
public class GenericServiceProvider<T> implements ServiceProvider<T> {
private final T service;
public GenericServiceProvider(T service) {
this.service = service;
}
public GenericServiceProvider(Supplier<T> serviceSupplier) {
this.service = serviceSupplier.get();
}
@Override
public T get() {
return service;
}
}

View file

@ -0,0 +1,8 @@
package de.kentoj.scrow.provider.provider;
public interface ServiceProvider<T> {
T get();
default void cleanup() {}
}

View file

@ -7,21 +7,27 @@ import java.util.UUID;
public interface EconomyService {
/**
* @throws IllegalArgumentException if amount < 0
*/
Mono<@NotNull Integer> getCoins(UUID playerId);
/**
* @param playerId uuid of player
* @throws IllegalArgumentException if amount < 0
*/
Mono<@NotNull Void> depositCoins(UUID playerId, int amount);
/**
* @param playerId uuid of player
* @return true on success, false on insufficient funds
* @throws IllegalArgumentException if amount < 0
*/
Mono<@NotNull Boolean> tryWithdrawCoins(UUID playerId, int amount);
/**
* @param playerId uuid of player
* @throws IllegalArgumentException if amount < 0
*/
Mono<@NotNull Void> setCoins(UUID playerId, int amount);
}

View file

@ -0,0 +1,10 @@
package de.kentoj.scrow.services.friends;
import java.time.Instant;
import java.util.UUID;
public interface FriendRequest {
UUID getFrom();
UUID getTo();
Instant getCreatedAt();
}

View file

@ -0,0 +1,11 @@
package de.kentoj.scrow.services.friends;
import de.kentoj.scrow.util.pair.Tuple2;
import java.time.Instant;
import java.util.UUID;
public interface Friendship {
Tuple2<UUID, UUID> getPlayerIds();
Instant getCreatedAt();
}

View file

@ -0,0 +1,9 @@
package de.kentoj.scrow.util.pair;
import lombok.Value;
@Value
public class Tuple2<A, B> {
A first;
B second;
}

View file

@ -0,0 +1,10 @@
package de.kentoj.scrow.util.pair;
import lombok.Value;
@Value
public class Tuple3<A, B, C> {
A first;
B second;
C third;
}

View file

@ -0,0 +1,11 @@
package de.kentoj.scrow.util.pair;
import lombok.Value;
@Value
public class Tuple4<A, B, C, D> {
A first;
B second;
C third;
D fourth;
}

View file

@ -0,0 +1,28 @@
package de.kentoj.scrow;
import reactor.core.publisher.Mono;
import java.util.UUID;
public class Main {
public static void main(String[] args) {
ScrowAPISurface.initScrowAPI(false);
{
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();
}
ScrowAPISurface.destroyScrowAPI();
}
}

View file

@ -0,0 +1,49 @@
package de.kentoj.scrow;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
import de.kentoj.scrow.economy.InMemoryEconomyService;
import de.kentoj.scrow.economy.MongoEconomyService;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.bson.UuidRepresentation;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.PojoCodecProvider;
import org.jetbrains.annotations.Nullable;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ScrowAPISurface {
private static @Nullable MongoClient databaseClient = null;
public static void initScrowAPI(boolean enableDatabase) {
if (enableDatabase) {
var pojoCodecProvider = PojoCodecProvider.builder().automatic(true).build();
var codecRegistry = CodecRegistries.fromRegistries(
MongoClientSettings.getDefaultCodecRegistry(),
CodecRegistries.fromProviders(pojoCodecProvider)
);
var settings = MongoClientSettings.builder()
.codecRegistry(codecRegistry)
.uuidRepresentation(UuidRepresentation.STANDARD)
.applyConnectionString(new ConnectionString("mongodb://localhost:27017/scrow"))
.build();
databaseClient = MongoClients.create(settings);
}
ScrowAPI.setEconomyService(
ScrowAPI.getDatabase() != null ?
new MongoEconomyService(ScrowAPI.getDatabase()) : new InMemoryEconomyService()
);
}
public static void destroyScrowAPI() {
if (databaseClient != null) {
databaseClient.close();
}
}
}

View file

@ -0,0 +1,47 @@
package de.kentoj.scrow.economy;
import com.google.common.base.Preconditions;
import de.kentoj.scrow.services.EconomyService;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class InMemoryEconomyService implements EconomyService {
private final Map<UUID, Integer> coins = new HashMap<>();
@Override
public Mono<@NotNull Integer> getCoins(UUID playerId) {
return Mono.fromSupplier(() -> coins.getOrDefault(playerId, 0));
}
@Override
public Mono<@NotNull Void> setCoins(UUID playerId, int amount) {
Preconditions.checkArgument(amount >= 0, "amount can not be negative");
return Mono.fromSupplier(() -> coins.put(playerId, amount)).then();
}
@Override
public Mono<@NotNull Void> depositCoins(UUID playerId, int amount) {
Preconditions.checkArgument(amount >= 0, "amount can not be negative");
return Mono.fromRunnable(() -> {
var cur = coins.getOrDefault(playerId, amount);
coins.put(playerId, cur + amount);
});
}
@Override
public Mono<@NotNull Boolean> tryWithdrawCoins(UUID playerId, int amount) {
Preconditions.checkArgument(amount >= 0, "amount can not be negative");
return Mono.fromSupplier(() -> {
var cur = coins.getOrDefault(playerId, 0);
if (cur < amount) return false;
coins.put(playerId, cur - amount);
return true;
});
}
}

View file

@ -1,28 +0,0 @@
package de.kentoj.scrow.economy;
import de.kentoj.scrow.services.EconomyService;
import reactor.core.publisher.Mono;
import java.util.UUID;
public class Main {
public static void main(String[] args) {
var con = new MongoDbHelper();
var playerId = UUID.randomUUID();
EconomyService economyService = new EconomyServiceImpl(con.getDatabase());
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();
con.close();
}
}

View file

@ -1,32 +0,0 @@
package de.kentoj.scrow.economy;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
import com.mongodb.reactivestreams.client.MongoDatabase;
import lombok.Getter;
import org.bson.UuidRepresentation;
import java.io.Closeable;
public class MongoDbHelper implements Closeable {
private final MongoClient con;
@Getter
private final MongoDatabase database;
public MongoDbHelper() {
var settings = MongoClientSettings.builder()
.applyConnectionString(new ConnectionString("mongodb://localhost:27017/scrow?ssl=false"))
.uuidRepresentation(UuidRepresentation.STANDARD)
.build();
con = MongoClients.create(settings);
database = con.getDatabase("scrow");
}
@Override
public void close() {
con.close();
}
}

View file

@ -13,12 +13,12 @@ import reactor.core.publisher.Mono;
import java.util.UUID;
public class EconomyServiceImpl implements EconomyService {
public class MongoEconomyService implements EconomyService {
private static final String COLLECTION_NAME = "economy";
private final @NotNull MongoDatabase database;
public EconomyServiceImpl(MongoDatabase database) {
public MongoEconomyService(MongoDatabase database) {
this.database = database;
Mono.from(database.createCollection(COLLECTION_NAME)).block();
}

View file

@ -0,0 +1,33 @@
package de.kentoj.scrow.friends;
import de.kentoj.scrow.services.friends.FriendRequest;
import de.kentoj.scrow.services.friends.Friendship;
import de.kentoj.scrow.util.pair.Tuple2;
import org.bson.codecs.OverridableUuidRepresentationUuidCodec;
import org.bson.types.ObjectId;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.UUID;
public interface FriendshipService {
Flux<@NotNull Friendship> getFriendships(UUID playerId);
/**
* @param initiator id of player who wished to end the friendship
* @param other id of the player the initiator wants to end the friendship with
* @return true on success, false if no such friendship existed.
*/
Mono<@NotNull Boolean> endFriendship(Tuple2<UUID, UUID> playerIds);
/**
* @param initiator id of player sending the friend request
* @param to id of the player the initiator wants to befriend
* @return true on success, false if already requested
*/
Mono<@NotNull Boolean> sendFriendRequest(UUID initiator, UUID to);
Mono<@NotNull Friendship> acceptFriendRequest(ObjectId objectId);
}

View file

@ -0,0 +1,36 @@
package de.kentoj.scrow.friends.friendship;
import com.google.common.base.Preconditions;
import de.kentoj.scrow.services.friends.Friendship;
import de.kentoj.scrow.util.pair.Tuple2;
import lombok.Value;
import org.bson.codecs.pojo.annotations.BsonCreator;
import org.bson.codecs.pojo.annotations.BsonProperty;
import org.bson.types.ObjectId;
import java.time.Instant;
import java.util.UUID;
@Value
public class BsonFriendship implements Friendship {
ObjectId id;
Tuple2<UUID, UUID> playerIds;
Instant createdAt;
@BsonCreator
public BsonFriendship(
@BsonProperty("_id") ObjectId id,
@BsonProperty("playerIds") Tuple2<UUID, UUID> playerIds,
@BsonProperty("createdAt") Instant createdAt
) {
Preconditions.checkArgument(playerIds.getFirst() != playerIds.getSecond());
this.id = id;
this.playerIds = toOrderedTuple(playerIds);
this.createdAt = createdAt;
}
public BsonFriendship(Tuple2<UUID, UUID> playerIds, Instant createdAt) {
this(ObjectId.get(), playerIds, createdAt);
}
}

View file

@ -0,0 +1,27 @@
package de.kentoj.scrow.friends.friendship;
import de.kentoj.scrow.services.friends.Friendship;
import de.kentoj.scrow.util.pair.Tuple2;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Instant;
import java.util.UUID;
public interface FriendshipRepository {
Flux<@NotNull Friendship> getFriendships(UUID playerId);
Mono<@NotNull Friendship> getFriendship(Tuple2<UUID, UUID> playerIds);
/**
* @return True if friendship saved, false if friendship already exists
*/
Mono<@NotNull Boolean> saveFriendship(Tuple2<UUID, UUID> playerIds, Instant createdAt);
/**
* @return True if friendship deleted, false if none found
*/
Mono<@NotNull Boolean> deleteFriendship(Tuple2<UUID, UUID> playerIds);
}

View file

@ -0,0 +1,22 @@
package de.kentoj.scrow.friends.friendship;
import de.kentoj.scrow.util.pair.Tuple2;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.UUID;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
class FriendshipUtils {
/**
* ensures constant order
* @return an ordered tuple
*/
public static Tuple2<UUID, UUID> toOrderedTuple(Tuple2<UUID, UUID> playerIds) {
if (playerIds.getFirst().compareTo(playerIds.getSecond()) < 0)
return new Tuple2<>(playerIds.getFirst(), playerIds.getSecond());
else
return new Tuple2<>(playerIds.getSecond(), playerIds.getFirst());
}
}

View file

@ -0,0 +1,86 @@
package de.kentoj.scrow.friends.friendship;
import com.mongodb.ErrorCategory;
import com.mongodb.MongoWriteException;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.Indexes;
import com.mongodb.reactivestreams.client.MongoCollection;
import com.mongodb.reactivestreams.client.MongoDatabase;
import de.kentoj.scrow.services.friends.Friendship;
import de.kentoj.scrow.util.pair.Tuple2;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Instant;
import java.util.UUID;
public class MongoFriendshipRepository implements FriendshipRepository {
private static final String COLLECTION_NAME = "friendships";
private final MongoDatabase database;
public MongoFriendshipRepository(MongoDatabase database) {
this.database = database;
database.createCollection(COLLECTION_NAME);
collection().createIndex(
Indexes.compoundIndex(
Indexes.ascending("playerIds.first"),
Indexes.ascending("playerIds.second")
),
new IndexOptions().unique(true)
);
}
@Override
public Flux<@NotNull Friendship> getFriendships(UUID playerId) {
return Flux.from(collection()
.find(Filters.or(
Filters.eq("playerIds.first", playerId),
Filters.eq("playerIds.second", playerId)
)));
}
@Override
public Mono<@NotNull Friendship> getFriendship(Tuple2<UUID, UUID> playerIds) {
playerIds = FriendshipUtils.toOrderedTuple(playerIds);
return Mono.from(collection()
.find(Filters.and(
Filters.eq("playerIds.first", playerIds.getFirst()),
Filters.eq("playerIds.second", playerIds.getSecond())
)));
}
@Override
public Mono<@NotNull Boolean> saveFriendship(Tuple2<UUID, UUID> playerIds, Instant createdAt) {
var friendship = new BsonFriendship(playerIds, createdAt);
return Mono.from(collection()
.insertOne(friendship)
)
.map(__ -> true)
.onErrorResume(e -> {
if (!(e instanceof MongoWriteException)) return Mono.error(e);
if (((MongoWriteException) e).getError().getCategory() != ErrorCategory.DUPLICATE_KEY)
return Mono.error(e);
return Mono.just(false);
});
}
@Override
public Mono<@NotNull Boolean> deleteFriendship(Tuple2<UUID, UUID> playerIds) {
playerIds = FriendshipUtils.toOrderedTuple(playerIds);
return Mono.from(collection()
.deleteOne(Filters.and(
Filters.eq("playerIds.first", playerIds.getFirst()),
Filters.eq("playerIds.second", playerIds.getSecond())
)))
.map(res -> res.getDeletedCount() > 0);
}
private MongoCollection<BsonFriendship> collection() {
return database.getCollection(COLLECTION_NAME, BsonFriendship.class);
}
}

View file

@ -0,0 +1,37 @@
package de.kentoj.scrow.friends.request;
import com.google.common.base.Preconditions;
import de.kentoj.scrow.services.friends.FriendRequest;
import lombok.Value;
import org.bson.codecs.pojo.annotations.BsonCreator;
import org.bson.codecs.pojo.annotations.BsonProperty;
import org.bson.types.ObjectId;
import java.time.Instant;
import java.util.UUID;
@Value
public class BsonFriendRequest implements FriendRequest {
ObjectId requestId;
UUID from;
UUID to;
Instant createdAt;
@BsonCreator
public BsonFriendRequest(
@BsonProperty("_id") ObjectId requestId,
@BsonProperty("from") UUID from,
@BsonProperty("to") UUID to,
@BsonProperty("createdAt") Instant createdAt
) {
Preconditions.checkArgument(from != to, "friend-request can not have same 'from' and 'to' value");
this.requestId = requestId;
this.from = from;
this.to = to;
this.createdAt = createdAt;
}
public BsonFriendRequest(UUID from, UUID to, Instant createdAt) {
this(ObjectId.get(), from, to, createdAt);
}
}

View file

@ -0,0 +1,28 @@
package de.kentoj.scrow.friends.request;
import de.kentoj.scrow.services.friends.FriendRequest;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Instant;
import java.util.UUID;
public interface FriendRequestRepository {
Flux<@NotNull FriendRequest> getIncomingFriendRequests(UUID playerId);
Flux<@NotNull FriendRequest> getOutgoingFriendRequests(UUID playerId);
Mono<@NotNull FriendRequest> getFriendRequest(UUID from, UUID to);
/**
* @return True if request saved, false if already existing request found.
*/
Mono<@NotNull Boolean> saveFriendRequest(UUID from, UUID to, Instant createdAt);
/**
* @return True if request deleted, false if none found.
*/
Mono<@NotNull Boolean> deleteFriendRequest(UUID from, UUID to);
}

View file

@ -0,0 +1,85 @@
package de.kentoj.scrow.friends.request;
import com.mongodb.ErrorCategory;
import com.mongodb.MongoWriteException;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.Indexes;
import com.mongodb.reactivestreams.client.MongoCollection;
import com.mongodb.reactivestreams.client.MongoDatabase;
import de.kentoj.scrow.services.friends.FriendRequest;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Instant;
import java.util.UUID;
public class MongoFriendRequestRepository implements FriendRequestRepository {
private static final String COLLECTION_NAME = "friend-requests";
private final MongoDatabase database;
public MongoFriendRequestRepository(MongoDatabase database) {
this.database = database;
database.createCollection(COLLECTION_NAME);
collection().createIndex(
Indexes.compoundIndex(
Indexes.ascending("from"),
Indexes.ascending("to")
),
new IndexOptions().unique(true)
);
}
@Override
public Mono<@NotNull FriendRequest> getFriendRequest(UUID from, UUID to) {
return Mono.from(collection()
.find(Filters.and(
Filters.eq("from", from),
Filters.eq("to", to)
))
.first());
}
@Override
public Mono<@NotNull Boolean> saveFriendRequest(UUID from, UUID to, Instant createdAt) {
var request = new BsonFriendRequest(from, to, createdAt);
return Mono.from(collection()
.insertOne(request)
)
.map(__ -> true)
.onErrorResume(e -> {
if (!(e instanceof MongoWriteException)) return Mono.error(e);
if (((MongoWriteException) e).getError().getCategory() != ErrorCategory.DUPLICATE_KEY)
return Mono.error(e);
return Mono.just(false);
});
}
@Override
public Flux<@NotNull FriendRequest> getIncomingFriendRequests(UUID playerId) {
return Flux.from(collection().find(Filters.eq("to", playerId)));
}
@Override
public Flux<@NotNull FriendRequest> getOutgoingFriendRequests(UUID playerId) {
return Flux.from(collection().find(Filters.eq("from", playerId)));
}
@Override
public Mono<@NotNull Boolean> deleteFriendRequest(UUID from, UUID to) {
return Mono.from(collection()
.deleteOne(Filters.and(
Filters.eq("from", from),
Filters.eq("to", to)
)))
.map(res -> res.getDeletedCount() > 0);
}
private MongoCollection<BsonFriendRequest> collection() {
return database.getCollection(COLLECTION_NAME, BsonFriendRequest.class);
}
}