diff --git a/README.md b/README.md index 28d9bf3..6f0db1b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,6 @@ An API designed to provide real-time access to a user's Discord data. ## TODO -- [ ] Caching +- [x] Caching - [ ] WebSockets - [ ] User account for extra data? (about me, connections, etc) \ No newline at end of file diff --git a/src/main/java/me/braydon/tether/model/CachedDiscordUser.java b/src/main/java/me/braydon/tether/model/CachedDiscordUser.java new file mode 100644 index 0000000..9fda83c --- /dev/null +++ b/src/main/java/me/braydon/tether/model/CachedDiscordUser.java @@ -0,0 +1,30 @@ +package me.braydon.tether.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import net.dv8tion.jda.api.entities.User; + +/** + * A model representing a cached Discord user. + * + * @author Braydon + */ +@AllArgsConstructor @Setter @Getter +public final class CachedDiscordUser { + /** + * The cached user. + */ + @NonNull private final User user; + + /** + * The cached user profile. + */ + @NonNull private final User.Profile profile; + + /** + * The unix time of when this user was cached. + */ + private long cached; +} \ No newline at end of file diff --git a/src/main/java/me/braydon/tether/service/DiscordService.java b/src/main/java/me/braydon/tether/service/DiscordService.java index 3ef69f5..1833237 100644 --- a/src/main/java/me/braydon/tether/service/DiscordService.java +++ b/src/main/java/me/braydon/tether/service/DiscordService.java @@ -1,5 +1,7 @@ package me.braydon.tether.service; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import jakarta.annotation.PostConstruct; import lombok.NonNull; import lombok.SneakyThrows; @@ -7,6 +9,7 @@ import lombok.extern.log4j.Log4j2; import me.braydon.tether.exception.impl.BadRequestException; import me.braydon.tether.exception.impl.ResourceNotFoundException; import me.braydon.tether.exception.impl.ServiceUnavailableException; +import me.braydon.tether.model.CachedDiscordUser; import me.braydon.tether.model.DiscordUser; import me.braydon.tether.model.response.DiscordUserResponse; import net.dv8tion.jda.api.JDA; @@ -18,6 +21,8 @@ import net.dv8tion.jda.api.utils.cache.CacheFlag; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import java.util.concurrent.TimeUnit; + /** * This service is responsible for * interacting with the Discord API. @@ -35,6 +40,13 @@ public final class DiscordService { */ private JDA jda; + /** + * A cache of users retrieved from Discord. + */ + private final Cache cachedUsers = Caffeine.newBuilder() + .expireAfterAccess(3L, TimeUnit.MINUTES) + .build(); + @PostConstruct public void onInitialize() { connectBot(); @@ -67,9 +79,21 @@ public final class DiscordService { break; } } - User user = jda.retrieveUserById(snowflake).complete(); - User.Profile profile = user.retrieveProfile().complete(); - return new DiscordUserResponse(DiscordUser.buildFromEntity(user, profile, member), -1L); + // Then retrieve the user + CachedDiscordUser cachedUser = cachedUsers.getIfPresent(snowflake); + boolean fromCache = cachedUser != null; + if (cachedUser == null) { // No cache, retrieve fresh data + User user = jda.retrieveUserById(snowflake).complete(); + cachedUser = new CachedDiscordUser( + user, user.retrieveProfile().complete(), System.currentTimeMillis() + ); + cachedUsers.put(snowflake, cachedUser); + } + // Finally build the response and respond with it + return new DiscordUserResponse( + DiscordUser.buildFromEntity(cachedUser.getUser(), cachedUser.getProfile(), member), + fromCache ? cachedUser.getCached() : -1L + ); } catch (ErrorResponseException ex) { // Failed to lookup the user, handle appropriately if (ex.getErrorCode() == 10013) {