nouritsu/src/main/java/nouritsu/Dao.java
2020-07-05 00:38:43 +02:00

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());
}
}