nouritsu/src/main/java/nouritsu/Dao.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());
}
}