99 lines
3.5 KiB
Java
99 lines
3.5 KiB
Java
package nouritsu;
|
|
|
|
import io.vavr.collection.HashSet;
|
|
import io.vavr.collection.List;
|
|
import io.vavr.control.Either;
|
|
import io.vavr.control.Option;
|
|
import nouritsu.types.Resp;
|
|
import nouritsu.types.Section;
|
|
import nouritsu.types.ShoppingItem;
|
|
import nouritsu.types.Store;
|
|
import redis.clients.jedis.Jedis;
|
|
import redis.clients.jedis.JedisPool;
|
|
|
|
import java.util.function.Function;
|
|
|
|
import static nouritsu.Serde.deserialize;
|
|
import static nouritsu.Serde.serialize;
|
|
|
|
public class Dao {
|
|
private final JedisPool pool;
|
|
// Prefixes for the redis keys because other applications might also be using the redis
|
|
private static final String PREFIX = "nouritsu_";
|
|
private static final String USER_PREFIX = PREFIX + "user_";
|
|
private static final String STORE_PREFIX = PREFIX + "store_";
|
|
private static final String ITEM_PREFIX = PREFIX + "item_";
|
|
|
|
Dao() {
|
|
pool = new JedisPool();
|
|
}
|
|
|
|
private <T> T withRedis(Function<Jedis, T> f) {
|
|
try (var redis = pool.getResource()) {
|
|
return f.apply(redis);
|
|
}
|
|
}
|
|
|
|
private Option<ShoppingItem> getItem(String name) {
|
|
return Option.of(withRedis(redis -> redis.get(ITEM_PREFIX + name)))
|
|
.map(item -> deserialize(item, ShoppingItem.class));
|
|
}
|
|
|
|
public String clearList(String id) {
|
|
withRedis(redis -> redis.del(USER_PREFIX + id));
|
|
// Just always return okay, even if we didn’t have data to begin with.
|
|
// Idempotency yay \o/
|
|
return created("Cleared list of user “%s“").formatted(id);
|
|
}
|
|
|
|
// The .exists() check is necessary because smembers throws an exception instead of returning null or an empty set.
|
|
private Option<HashSet<String>> getSet(String key) {
|
|
return withRedis(redis ->
|
|
redis.exists(key)
|
|
? Option.some(HashSet.ofAll(redis.smembers(key)))
|
|
: Option.none()
|
|
);
|
|
}
|
|
|
|
public Either<Resp, HashSet<ShoppingItem>> getList(String id) {
|
|
return getSet(USER_PREFIX + id)
|
|
.map(items -> items.map(this::getItem).map(Option::get))
|
|
.toEither(Resp.NOT_FOUND.apply("No list found for user “%s”".formatted(id)));
|
|
}
|
|
|
|
public Either<Resp, Store> getStore(String name) {
|
|
return withRedis(redis -> Option.of(redis.get(STORE_PREFIX + name)))
|
|
.toEither(Resp.BAD_REQUEST.apply("Store %s not found".formatted(name)))
|
|
.map(s -> deserialize(s, Store.class));
|
|
}
|
|
|
|
// Check if an item is known and add it to the list if it is.
|
|
public Either<Resp, String> addToList(String id, String item) {
|
|
return getItem(item).map(it -> {
|
|
withRedis(redis -> redis.sadd(USER_PREFIX + id, item));
|
|
return created("Added “%s“ to list of user “%s”").formatted(it, id);
|
|
}).toEither(Resp.BAD_REQUEST.apply("Item %s not found".formatted(item)));
|
|
}
|
|
|
|
public String addItem(ShoppingItem item) {
|
|
withRedis(redis -> redis.set(ITEM_PREFIX + item.name(), serialize(item)));
|
|
return created("Registered new item “%s” under the section “%s”")
|
|
.formatted(item.name(), item.section().name().toLowerCase());
|
|
}
|
|
|
|
private String created(String msg) {
|
|
return "{\"msg\": \"%s\"}".formatted(msg);
|
|
}
|
|
|
|
public String addStore(Store store) {
|
|
// just overwrite it if we already know that store
|
|
withRedis(redis ->
|
|
redis.del(STORE_PREFIX + store.name())
|
|
// This plus doesn’t actually add anything.
|
|
// It just allows us to do both steps in one statement in the lambda body.
|
|
+ redis.set(STORE_PREFIX + store.name(), serialize(store))
|
|
);
|
|
return created("Registered new store “%s” with the sections [%s]").formatted(store.name(), store.sections());
|
|
}
|
|
}
|