101 lines
3.6 KiB
Java
101 lines
3.6 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;
|
|
|
|
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)))
|
|
.flatMap(section -> Section.tryParse(section).toOption())
|
|
.map(section -> new ShoppingItem(name, section));
|
|
}
|
|
|
|
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 "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.lrange(STORE_PREFIX + name, 0, 1000L))
|
|
.toEither(Resp.BAD_REQUEST.apply("Store %s not found".formatted(name)))
|
|
.map(sections -> sections.stream()
|
|
.map(Section::tryParse)
|
|
// Safe; elements in persistence must have a valid section
|
|
.map(Either::get)
|
|
.collect(List.collector()))
|
|
.map(sections -> new Store(name, sections))
|
|
);
|
|
}
|
|
|
|
// 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 "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(), item.section().name()));
|
|
return "Registered new item “%s” under the section “%s”"
|
|
.formatted(item.name(), item.section().name().toLowerCase());
|
|
}
|
|
|
|
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.rpush(
|
|
STORE_PREFIX + store.name(),
|
|
store.sections().map(Section::name).toJavaArray(String[]::new)
|
|
)
|
|
);
|
|
return "Registered new store “%s” with the sections [%s]".formatted(store.name(), store.sections());
|
|
}
|
|
}
|