Skip to content

Commit

Permalink
feat: add cache for listing users, choreTypes, chores and weeklyChores (
Browse files Browse the repository at this point in the history
  • Loading branch information
sralloza authored Jan 16, 2023
1 parent 89d315c commit 4b1d1ee
Show file tree
Hide file tree
Showing 30 changed files with 485 additions and 140 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ Configuration is done by setting environment variables.
- ***API_BASE_URL***: base url where the Chore Management API is deployed.
- ***API_HTTP2***: Enable HTTP/2. Defaults to `true`.
- ***LATEX_CACHE_ENABLED***: Enable the cache for latex generated images. Defaults to `true`.
- ***USERS_CACHE***: Enable or disable the users cache. If is disabled, for every message sent to the bot a GET request will be sent to the API. Designed to be used only in testing mode. Defaults to `true`.
- ***CHORE_TYPES_CACHE_ENABLED***: Enable or disable the users cache. If is disabled, for every message sent to the bot a GET request will be sent to the API. Defaults to `true`.
- ***CHORES_CACHE_ENABLED***: Enable or disable the chores cache. It manages the `listWeeklyChores` and the `listChores` endpoints. Defaults to `true`.
- ***USERS_CACHE_ENABLED***: Enable or disable the users cache. If is disabled, for every message sent to the bot a GET request will be sent to the API. Defaults to `true`.
10 changes: 5 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,21 @@ dependencies {
implementation 'org.scilab.forge:jlatexmath:1.0.7'

// Json encoding
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.3'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.0'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.0'

// Redis
implementation 'redis.clients:jedis:4.2.3'
implementation 'redis.clients:jedis:4.3.1'

// Telegram
implementation 'org.telegram:telegrambots:6.1.0'
implementation 'org.telegram:telegrambots-abilities:6.1.0'

// Logging
implementation 'org.slf4j:slf4j-api:1.7.36'
implementation 'com.tersesystems.logback:logback-classic:1.0.3'
implementation 'com.tersesystems.logback:logback-classic:1.1.1'
implementation 'com.tersesystems.logback:logback-structured-config:1.0.3'
implementation 'com.tersesystems.logback:logback-typesafe-config:1.0.3'
implementation 'com.tersesystems.logback:logback-typesafe-config:1.1.1'
implementation 'net.logstash.logback:logstash-logback-encoder:7.2'
}

Expand Down
6 changes: 6 additions & 0 deletions src/main/java/Module.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import com.typesafe.config.ConfigFactory;
import repositories.ChoreManagementRepository;
import repositories.ChoreManagementRepositoryImp;
import repositories.chores.ChoresRepositoryCacheableModule;
import repositories.choretypes.ChoreTypesRepositoryCacheableModule;
import repositories.users.UsersRepositoryCacheableModule;
import security.Security;
import security.SecurityImp;
import services.ChoreManagementService;
Expand All @@ -18,6 +21,9 @@ public class Module extends AbstractModule {
@Override
protected void configure() {
install(new LatexCacheableModule());
install(new ChoreTypesRepositoryCacheableModule());
install(new UsersRepositoryCacheableModule());
install(new ChoresRepositoryCacheableModule());

Config config = ConfigFactory.load();
String botName = config.getString("telegram.bot.username");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package services;
package base;

import com.google.inject.AbstractModule;
import com.typesafe.config.ConfigFactory;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class CacheableModule extends AbstractModule {
public <T> Class<? extends T> getServiceByConfig(String tag, Class<? extends T> cachedClass, Class<? extends T> nonCachedClass) {
public <T> Class<? extends T> getComponentByConfig(String tag, Class<? extends T> cachedClass, Class<? extends T> nonCachedClass) {
boolean result = ConfigFactory.load().getBoolean("cache." + tag + ".enabled");
log.info("Cache for " + tag + " is " + (result ? "enabled" : "disabled"));
return result ? cachedClass : nonCachedClass;
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/bot/BaseChoreManagementBot.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ protected void sendMessage(String msgStr, String chatId, boolean markdown) {
}
}

protected boolean requireUser(MessageContext ctx) {
protected Boolean requireUser(MessageContext ctx) {
var chatId = ctx.chatId().toString();
if (!security.isAuthenticated(chatId)) {
var isAuthenticated = security.isAuthenticated(chatId).join();
if (!isAuthenticated) {
sendMessage("You don't have permission to execute this action", chatId, false);
return false;
}
Expand Down
11 changes: 5 additions & 6 deletions src/main/java/bot/ChoreManagementBot.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import java.util.stream.Collectors;

import static org.telegram.abilitybots.api.objects.Locality.USER;
import static org.telegram.abilitybots.api.objects.Privacy.CREATOR;
import static org.telegram.abilitybots.api.objects.Privacy.PUBLIC;

@Slf4j
Expand Down Expand Up @@ -176,19 +175,19 @@ private void processMsg(MessageContext ctx) {

switch (userMessage) {
case UserMessages.TICKETS:
choreTypesFuture = service.getChoreTypes();
choreTypesFuture = service.listChoreTypes();
service.getTickets(chatId)
.thenCombine(choreTypesFuture, Normalizers::normalizeTickets)
.thenAcceptAsync(tickets -> sendTable(tickets, chatId, TICKETS_TABLE_PNG, Messages.NO_TICKETS_FOUND), executor);
break;
case UserMessages.TASKS:
choreTypesFuture = service.getChoreTypes();
choreTypesFuture = service.listChoreTypes();
service.getWeeklyChores(chatId)
.thenCombine(choreTypesFuture, Normalizers::normalizeWeeklyChores)
.thenAcceptAsync(tasks -> sendTable(tasks, chatId, WEEKLY_TASKS_TABLE_PNG, Messages.NO_TASKS), executor);
break;
case UserMessages.COMPLETE_TASK:
choreTypesFuture = service.getChoreTypes();
choreTypesFuture = service.listChoreTypes();
service.getChores(chatId)
.thenCombineAsync(choreTypesFuture, (choreList, choreTypeList) ->
startFlowSelectTask(ctx, choreList, choreTypeList), executor);
Expand Down Expand Up @@ -216,7 +215,7 @@ private void processReplyMsg(MessageContext ctx) {
.handle(replyHandler(ctx, "Week skipped: " + userMessage));
break;
case Messages.ASK_FOR_WEEK_TO_UNSKIP:
service.unskipWeek(chatId, userMessage)
service.unSkipWeek(chatId, userMessage)
.handle(replyHandler(ctx, "Week unskipped: " + userMessage));
break;
default:
Expand All @@ -233,7 +232,7 @@ private void processQueryData(MessageContext ctx) {

switch (callbackData.getType()) {
case COMPLETE_TASK:
service.completeTask(chatId, callbackData.getWeekId(), callbackData.getChoreType())
service.completeChore(chatId, callbackData.getWeekId(), callbackData.getChoreType())
.handle(callbackQueryHandler(ctx, queryId, Messages.TASK_COMPLETED, QueryType.COMPLETE_TASK));
break;
default:
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/constants/CacheConstants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package constants;

public class CacheConstants {
public static final int LATEX_CACHE_EXPIRE_SECONDS = 2 * 7 * 24 * 3600;
public static final int USERS_CACHE_EXPIRE_SECONDS = 4 * 7 * 24 * 3600;
public static final int CHORE_TYPES_CACHE_EXPIRE_SECONDS = 4 * 7 * 24 * 3600;
public static final int CHORES_CACHE_EXPIRE_SECONDS = 7 * 24 * 3600;
public static final int WEEKLY_CHORES_CACHE_EXPIRE_SECONDS = 7 * 24 * 3600;

public static final String USERS_REDIS_KEY_PREFIX = "api::users";
public static final String CHORES_REDIS_KEY_PREFIX = "api::chores";
public static final String WEEKLY_CHORES_REDIS_KEY_PREFIX = "api::weeklyChores";
}
37 changes: 23 additions & 14 deletions src/main/java/repositories/BaseRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,14 @@
public class BaseRepository {
private static final Set<Integer> VALID_STATUS_CODES = Set.of(200, 201, 204);
private final String baseURL;
private final String apiToken;
private final String adminApiKey;
private final boolean http2;
private final Security security;
protected final Executor executor;

public BaseRepository(String baseURL, String apiToken, ConfigRepository config,
Security security, Executor executor) {
this.baseURL = baseURL;
this.apiToken = apiToken;
public BaseRepository(ConfigRepository config, Security security, Executor executor) {
this.baseURL = config.getString("api.baseURL");
this.adminApiKey = config.getString("api.adminApiKey");
this.http2 = config.getBoolean("api.http2");
this.security = security;
this.executor = executor;
Expand All @@ -47,21 +46,21 @@ private HttpClient.Version getHttpClientVersion() {
}

protected <T> CompletableFuture<T> sendGetRequest(String path, Class<T> clazz, String userId) {
String token = security.getTenantToken(userId);
return sendRequest("GET", path, clazz, token, null);
return security.getUserApiKey(userId)
.thenComposeAsync(token -> sendRequest("GET", path, clazz, token, null), executor);
}

protected <T> CompletableFuture<T> sendPostRequest(String path, Class<T> clazz, String userId) {
String token = security.getTenantToken(userId);
return sendRequest("POST", path, clazz, token, null);
return security.getUserApiKey(userId)
.thenComposeAsync(token -> sendRequest("POST", path, clazz, token, null), executor);
}

protected <T> CompletableFuture<T> sendPostRequestAdmin(String path, Class<T> clazz) {
return sendRequest("POST", path, clazz, apiToken, null);
return sendRequest("POST", path, clazz, adminApiKey, null);
}

protected <T> CompletableFuture<T> sendGetRequestAdmin(String path, Class<T> clazz) {
return sendRequest("GET", path, clazz, apiToken, null);
return sendRequest("GET", path, clazz, adminApiKey, null);
}

private <T> CompletableFuture<T> sendRequest(String method, String path, Class<T> clazz,
Expand Down Expand Up @@ -111,10 +110,10 @@ private <T> CompletableFuture<T> sendRequest(String method, String path, Class<T

return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApplyAsync(this::getBodyOpt, executor)
.thenApplyAsync(bodyOpt -> bodyOpt.map(body -> processBody(body, clazz)).orElse(null), executor);
.thenApplyAsync(bodyOpt -> bodyOpt.map(body -> fromJson(body, clazz)).orElse(null), executor);
}

private <T> T processBody(String body, Class<T> clazz) {
protected <T> T fromJson(String body, Class<T> clazz) {
if (clazz == null) {
return null;
}
Expand All @@ -123,7 +122,17 @@ private <T> T processBody(String body, Class<T> clazz) {
try {
return mapper.readValue(body, clazz);
} catch (JsonProcessingException e) {
log.error("Error parsing response: " + body, e);
log.error("Error parsing json: " + body, e);
return null;
}
}

protected String toJson(Object object) {
ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
try {
return mapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
log.error("Error parsing json: " + object, e);
return null;
}
}
Expand Down
18 changes: 0 additions & 18 deletions src/main/java/repositories/ChoreManagementRepository.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,12 @@
package repositories;

import models.Chore;
import models.ChoreType;
import models.Ticket;
import models.User;
import models.WeeklyChores;

import java.util.List;
import java.util.concurrent.CompletableFuture;

public interface ChoreManagementRepository {
CompletableFuture<List<Ticket>> getTickets(String userId);

CompletableFuture<List<WeeklyChores>> getWeeklyChores(String userId);

CompletableFuture<Void> completeTask(String userId, String weekId, String choreType);

CompletableFuture<List<Chore>> getChores(String userId);

CompletableFuture<Void> skipWeek(String userId, String weekId);

CompletableFuture<Void> unskipWeek(String userId, String weekId);

CompletableFuture<WeeklyChores> createWeeklyChores(String weekId);

CompletableFuture<List<User>> listUsersAdminToken();

CompletableFuture<List<ChoreType>> getChoreTypes();
}
32 changes: 1 addition & 31 deletions src/main/java/repositories/ChoreManagementRepositoryImp.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,49 +17,19 @@
public class ChoreManagementRepositoryImp extends BaseRepository implements ChoreManagementRepository {
@Inject
public ChoreManagementRepositoryImp(ConfigRepository config, Security security, Executor executor) {
super(config.getString("api.baseURL"), config.getString("api.adminApiKey"), config, security, executor);
super(config, security, executor);
}

public CompletableFuture<List<Ticket>> getTickets(String userId) {
return sendGetRequest("/api/v1/tickets", Ticket[].class, userId)
.thenApply(Arrays::asList);
}

public CompletableFuture<List<WeeklyChores>> getWeeklyChores(String userId) {
return sendGetRequest("/api/v1/weekly-chores?missing_only=true", WeeklyChores[].class, userId)
.thenApply(Arrays::asList);
}

public CompletableFuture<Void> completeTask(String userId, String weekId, String choreType) {
String path = "/api/v1/chores/" + weekId + "/type/" + choreType + "/complete";
return sendPostRequest(path, null, userId);
}

public CompletableFuture<List<Chore>> getChores(String userId) {
return sendGetRequest("/api/v1/chores?user_id=me&done=false", Chore[].class, userId)
.thenApply(Arrays::asList);
}

public CompletableFuture<Void> skipWeek(String userId, String weekId) {
return sendPostRequest("/api/v1/users/me/deactivate/" + weekId, null, userId);
}

public CompletableFuture<Void> unskipWeek(String userId, String weekId) {
return sendPostRequest("/api/v1/users/me/reactivate/" + weekId, null, userId);
}

public CompletableFuture<WeeklyChores> createWeeklyChores(String weekId) {
return sendPostRequestAdmin("/api/v1/weekly-chores/" + weekId, WeeklyChores.class);
}

public CompletableFuture<List<User>> listUsersAdminToken() {
return sendGetRequestAdmin("/api/v1/users", User[].class)
.thenApply(Arrays::asList);
}

@Override
public CompletableFuture<List<ChoreType>> getChoreTypes() {
return sendGetRequestAdmin("/api/v1/chore-types", ChoreType[].class)
.thenApply(Arrays::asList);
}
}
14 changes: 14 additions & 0 deletions src/main/java/repositories/chores/ChoresRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package repositories.chores;

import models.Chore;
import models.WeeklyChores;

import java.util.List;
import java.util.concurrent.CompletableFuture;

public interface ChoresRepository {
CompletableFuture<List<Chore>> listChores(String userId);
CompletableFuture<List<WeeklyChores>> listWeeklyChores(String userId);
CompletableFuture<WeeklyChores> createWeeklyChores(String weekId);
CompletableFuture<Void> completeChore(String userId, String weekId, String choreType);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package repositories.chores;

import base.CacheableModule;

public class ChoresRepositoryCacheableModule extends CacheableModule {
@Override
protected void configure() {
bind(ChoresRepository.class).to(getComponentByConfig(
"chores",
ChoresRepositoryCached.class,
ChoresRepositoryNonCached.class));
}
}
Loading

0 comments on commit 4b1d1ee

Please sign in to comment.