.
This commit is contained in:
parent
07fa0efab7
commit
145a3116bc
29 changed files with 721 additions and 62 deletions
11
.idea/inspectionProfiles/Project_Default.xml
generated
11
.idea/inspectionProfiles/Project_Default.xml
generated
|
|
@ -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" />
|
||||
|
|
|
|||
29
core-api/src/main/java/de/kentoj/scrow/ScrowAPI.java
Normal file
29
core-api/src/main/java/de/kentoj/scrow/ScrowAPI.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package de.kentoj.scrow.provider.provider;
|
||||
|
||||
public interface ServiceProvider<T> {
|
||||
|
||||
T get();
|
||||
|
||||
default void cleanup() {}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package de.kentoj.scrow.util.pair;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
public class Tuple2<A, B> {
|
||||
A first;
|
||||
B second;
|
||||
}
|
||||
10
core-api/src/main/java/de/kentoj/scrow/util/pair/Tuple3.java
Normal file
10
core-api/src/main/java/de/kentoj/scrow/util/pair/Tuple3.java
Normal 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;
|
||||
}
|
||||
11
core-api/src/main/java/de/kentoj/scrow/util/pair/Tuple4.java
Normal file
11
core-api/src/main/java/de/kentoj/scrow/util/pair/Tuple4.java
Normal 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;
|
||||
}
|
||||
28
core-impl/src/main/java/de/kentoj/scrow/Main.java
Normal file
28
core-impl/src/main/java/de/kentoj/scrow/Main.java
Normal 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();
|
||||
}
|
||||
}
|
||||
49
core-impl/src/main/java/de/kentoj/scrow/ScrowAPISurface.java
Normal file
49
core-impl/src/main/java/de/kentoj/scrow/ScrowAPISurface.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue