Skip to content

Commit

Permalink
Merge pull request #77 from Tune-Fun/feature/interaction/like
Browse files Browse the repository at this point in the history
Feature/interaction/like
  • Loading branch information
habinkim authored May 25, 2024
2 parents 3d226e9 + a7b486c commit d2b1675
Show file tree
Hide file tree
Showing 66 changed files with 1,431 additions and 1,182 deletions.
8 changes: 4 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ ext {
set('awspringVersion', "3.1.1")
set('awssdkVersion', "2.25.45")
set('jwtVersion', '0.12.5')
set('queryDslVersion', "6.2.1")
set('queryDslVersion', "6.3")
set('mapstructVersion', "1.5.5.Final")
}

Expand All @@ -82,7 +82,7 @@ dependencies {
implementation "io.github.openfeign.querydsl:querydsl-collections:${queryDslVersion}"
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.1'
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-database-postgresql:10.12.0'
implementation 'org.flywaydb:flyway-database-postgresql:10.13.0'
implementation 'org.postgresql:postgresql'

// development
Expand All @@ -108,7 +108,7 @@ dependencies {
implementation 'software.amazon.cryptography:aws-cryptographic-material-providers:1.3.0'
implementation 'org.springframework.boot:spring-boot-starter-batch'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.1.1'
implementation 'com.slack.api:slack-app-backend:1.39.0'
implementation 'com.slack.api:slack-app-backend:1.39.2'
implementation 'io.netty:netty-resolver-dns-native-macos:4.1.108.Final:osx-aarch_64'

// Message Broker
Expand All @@ -130,7 +130,7 @@ dependencies {
implementation "org.mapstruct:mapstruct:${mapstructVersion}"
implementation "org.mapstruct:mapstruct-processor:${mapstructVersion}"
compileOnly 'org.projectlombok:lombok'
implementation 'com.google.guava:guava:33.1.0-jre'
implementation 'com.google.guava:guava:33.2.0-jre'
implementation 'org.apache.commons:commons-lang3:3.14.0'
implementation 'org.apache.commons:commons-rng-simple:1.5'
implementation 'commons-io:commons-io:2.16.1'
Expand Down
3 changes: 2 additions & 1 deletion src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ include::common/message.adoc[]
include::account-api.adoc[]
include::oauth2-api.adoc[]
include::otp-api.adoc[]
include::vote-api.adoc[]
include::vote-api.adoc[]
include::interaction-api.adoc[]
14 changes: 14 additions & 0 deletions src/docs/asciidoc/interaction-api.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[[Interaction-API]]
== Like API

=== 투표 게시물 좋아요 등록 API

==== 성공

operation::like-vote-paper-success[snipeets='http-request,http-response,path-parameter,response-fields']

=== 투표 게시물 좋아요 취소 API

==== 성공

operation::unlike-vote-paper-success[snipeets='http-request,http-response,path-parameter,response-fields']
21 changes: 16 additions & 5 deletions src/main/java/com/tune_fun/v1/common/config/RedisConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.tune_fun.v1.account.adapter.output.persistence.jwt.AccessTokenRedisEntity;
import com.tune_fun.v1.account.adapter.output.persistence.jwt.RefreshTokenRedisEntity;
import com.tune_fun.v1.common.constant.Constants;
import com.tune_fun.v1.common.util.count.AtomicCounter;
import com.tune_fun.v1.otp.adapter.output.persistence.OtpRedisEntity;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
Expand All @@ -25,7 +27,11 @@
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

import static java.time.Duration.ofDays;
import static java.time.Duration.ofHours;
import static org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair.fromSerializer;

@Slf4j
Expand Down Expand Up @@ -69,14 +75,19 @@ public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory)
.prefixCacheNameWith("Cache_")
.entryTtl(Duration.ofMinutes(30));

Map<String, RedisCacheConfiguration> redisCacheConfigMap = new HashMap<>();
redisCacheConfigMap.put(Constants.CacheNames.VOTE_PAPER, redisCacheConfiguration.entryTtl(ofHours(1)));
redisCacheConfigMap.put(Constants.CacheNames.VOTE_CHOICE, redisCacheConfiguration.entryTtl(ofDays(1)));

return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory)
.cacheDefaults(redisCacheConfiguration)
.withInitialCacheConfigurations(redisCacheConfigMap)
.build();
}

@Bean
public RedisTemplate<String, AccessTokenRedisEntity> redisTemplateForAccessToken() throws JsonProcessingException {
public RedisTemplate<String, AccessTokenRedisEntity> redisTemplateForAccessToken() {
RedisTemplate<String, AccessTokenRedisEntity> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
Expand All @@ -85,7 +96,7 @@ public RedisTemplate<String, AccessTokenRedisEntity> redisTemplateForAccessToken
}

@Bean
public RedisTemplate<String, RefreshTokenRedisEntity> redisTemplateForRefreshToken() throws JsonProcessingException {
public RedisTemplate<String, RefreshTokenRedisEntity> redisTemplateForRefreshToken() {
RedisTemplate<String, RefreshTokenRedisEntity> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
Expand All @@ -94,7 +105,7 @@ public RedisTemplate<String, RefreshTokenRedisEntity> redisTemplateForRefreshTok
}

@Bean
public RedisTemplate<String, OtpRedisEntity> redisTemplateForOtp() throws JsonProcessingException {
public RedisTemplate<String, OtpRedisEntity> redisTemplateForOtp() {
RedisTemplate<String, OtpRedisEntity> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
Expand All @@ -103,14 +114,14 @@ public RedisTemplate<String, OtpRedisEntity> redisTemplateForOtp() throws JsonPr
}

@Bean
public StringRedisTemplate stringRedisTemplate() throws JsonProcessingException {
public StringRedisTemplate stringRedisTemplate() {
StringRedisTemplate redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}

@Bean
public RedisTemplate<String, Object> redisTemplateForObject() throws JsonProcessingException {
public RedisTemplate<String, Object> redisTemplateForObject() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/tune_fun/v1/common/config/Uris.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ private Uris() {

public static final String REGISTER_VOTE = VOTE_ROOT + "/{votePaperId}" + "/register" + "/{voteChoiceId}";

public static final String LIKE_ROOT = API_V1_ROOT + "/likes";

public static final String LIKE_VOTE_PAPER = LIKE_ROOT + "/{votePaperId}";

public static final String SWAGGER_UI_ROOT = "/swagger-ui";
public static final String SWAGGER_UI = "/swagger-ui.html";
public static final String SWAGGER_DOCS = "/docs/com.tune_fun-open-api-3.0.1.json";
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/com/tune_fun/v1/common/constant/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public final class Constants {

public static final String COLON = ":";

public static final String DOUBLE_COLON = "::";

public static final String SEMICOLON = ";";

public static final String DOT = ".";
Expand All @@ -30,6 +32,11 @@ public final class Constants {
LocalDate.of(1970, 1, 1), LocalTime.of(0, 0, 1)
);

public static final class CacheNames {
public static final String VOTE_PAPER = "votePaper";
public static final String VOTE_CHOICE = "voteChoice";
}

public static final class NicknameFragment {

public static final String[] PREFIX_NAMES = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
*/
@Component
@Order(HIGHEST_PRECEDENCE)
class MDCLoggingFilter implements Filter {
public class MDCLoggingFilter implements Filter {

@Override
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
MDC.put("CORRELATION_ID", StringUtil.uuid());
MDC.put("CORRELATION_ID", StringUtil.ulid());
filterChain.doFilter(servletRequest, servletResponse);
MDC.clear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public enum MessageCode {
VOTE_POLICY_OFFERS_COUNT_SHOULD_BE_MORE_THAN_TWO(BAD_REQUEST, "3207"),
VOTE_POLICY_ONLY_REGISTER_CHOICE_ON_ALLOW_ADD_CHOICES_OPTION(BAD_REQUEST, "3208"),
VOTE_POLICY_ONE_VOTE_CHOICE_PER_USER_ON_VOTE_PAPER(BAD_REQUEST, "3209"),
VOTE_POLICY_ALREADY_LIKED_VOTE_PAPER(BAD_REQUEST, "3210"),

// 분산 락 관련
LOCK_ACQUISITION_FAILED_ERROR(INTERNAL_SERVER_ERROR, "4001"),
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/com/tune_fun/v1/common/util/count/AtomicCounter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.tune_fun.v1.common.util.count;

import java.io.Serial;
import java.io.Serializable;
import java.util.concurrent.atomic.LongAdder;

public final class AtomicCounter implements Counter, Serializable {

@Serial
private static final long serialVersionUID = 119874651351L;

private final LongAdder concreteCounter = new LongAdder();

public void increment(long i) {
concreteCounter.add(i);
}

@Override
public void increment() {
concreteCounter.increment();
}

@Override
public void decrement() {
concreteCounter.decrement();
}

@Override
public long get() {
return concreteCounter.sum();
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/tune_fun/v1/common/util/count/Counter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.tune_fun.v1.common.util.count;

public sealed interface Counter permits AtomicCounter {
void increment();

void decrement();

long get();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.tune_fun.v1.interaction.adapter.input.rest;

import com.tune_fun.v1.account.domain.value.CurrentUser;
import com.tune_fun.v1.common.config.Uris;
import com.tune_fun.v1.common.hexagon.WebAdapter;
import com.tune_fun.v1.common.response.Response;
import com.tune_fun.v1.common.response.ResponseMapper;
import com.tune_fun.v1.interaction.application.port.input.usecase.LikeVotePaperUseCase;
import com.tune_fun.v1.interaction.application.port.input.usecase.UnlikeVotePaperUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@WebAdapter
@RequiredArgsConstructor
public class LikeController {

private final LikeVotePaperUseCase likeVotePaperUseCase;
private final UnlikeVotePaperUseCase unlikeVotePaperUseCase;

private final ResponseMapper responseMapper;

@PostMapping(value = Uris.LIKE_VOTE_PAPER)
public ResponseEntity<Response<?>> likeVotePaper(@PathVariable(name = "votePaperId") Long votePaperId, @CurrentUser final User user) {
likeVotePaperUseCase.likeVotePaper(votePaperId, user);
return responseMapper.ok();
}

@DeleteMapping(value = Uris.LIKE_VOTE_PAPER)
public ResponseEntity<Response<?>> unlikeVotePaper(@PathVariable(name = "votePaperId") Long votePaperId, @CurrentUser final User user) {
unlikeVotePaperUseCase.unlikeVotePaper(votePaperId, user);
return responseMapper.ok();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.tune_fun.v1.interaction.adapter.input.scheduler;

import com.tune_fun.v1.common.util.StringUtil;
import com.tune_fun.v1.interaction.application.port.input.usecase.UpdateVotePaperStatisticsUseCase;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class LikeCountAggregationScheduler {

private final UpdateVotePaperStatisticsUseCase updateVotePaperStatisticsUseCase;

@Scheduled(fixedDelay = 1000L * 5L)
public void aggregateLikeCount() {
MDC.put("CORRELATION_ID", StringUtil.ulid());
log.info("Start to aggregate Vote Paper like count");
updateVotePaperStatisticsUseCase.updateVotePaperStatistics();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.tune_fun.v1.interaction.adapter.output.persistence;

import com.tune_fun.v1.interaction.application.port.output.LoadVotePaperLikeCountPort;
import com.tune_fun.v1.interaction.application.port.output.SaveVotePaperLikeCountPort;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Optional;
import java.util.Set;

import static com.tune_fun.v1.common.constant.Constants.DOUBLE_COLON;

@Slf4j
@Component
@RequiredArgsConstructor
public class LikeCountPersistenceAdapter implements SaveVotePaperLikeCountPort, LoadVotePaperLikeCountPort {

private static final String VOTE_PAPER_LIKE_COUNT_KEY = "vote_paper_like_count";

private final RedisTemplate<String, Object> redisTemplate;

@Override
public Set<String> getVotePaperLikeCountKeys() {
return redisTemplate.keys(VOTE_PAPER_LIKE_COUNT_KEY + "*");
}

@Override
public Long getVotePaperLikeCount(String key) {
log.info("Get vote paper like count for key: {}", key);
String value = String.valueOf(redisTemplate.opsForValue().get(key));
return Long.parseLong(value);
}

@Override
public void incrementVotePaperLikeCount(final Long votePaperId) {
getVotePaperLikeCountById(votePaperId).ifPresentOrElse(
likeCount -> redisTemplate.opsForValue().increment(getKey(votePaperId)),
() -> redisTemplate.opsForValue().set(getKey(votePaperId), 1)
);
}

@Override
public void decrementVotePaperLikeCount(final Long votePaperId) {
Optional<Object> votePaperLikeCountById = getVotePaperLikeCountById(votePaperId);

if (votePaperLikeCountById.isEmpty()) {
redisTemplate.opsForValue().set(getKey(votePaperId), 0);
return;
}

log.info("vote paper like count : {}", votePaperLikeCountById.get());

if (Long.parseLong(String.valueOf(votePaperLikeCountById.get())) == 0)
return;

redisTemplate.opsForValue().decrement(getKey(votePaperId));
}

@Override
public String getKey(final Long votePaperId) {
return VOTE_PAPER_LIKE_COUNT_KEY + DOUBLE_COLON + votePaperId;
}

@Override
public Long getVotePaperId(final String key) {
return Long.parseLong(key.split(DOUBLE_COLON)[1]);
}

@Override
public Optional<Object> getVotePaperLikeCountById(final Long votePaperId) {
return Optional.ofNullable(redisTemplate.opsForValue().get(getKey(votePaperId)));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.tune_fun.v1.interaction.adapter.output.persistence;

import com.tune_fun.v1.vote.domain.value.RegisteredVotePaperLike;

import java.util.Optional;

public interface VotePaperLikeCustomRepository {

Optional<RegisteredVotePaperLike> findByVotePaperIdAndLikerUsername(final Long votePaperId, final String username);

void deleteByVotePaperIdAndLikerUsername(final Long votePaperId, final String username);

}
Loading

0 comments on commit d2b1675

Please sign in to comment.