Skip to content

Commit

Permalink
[FEAT] 일정 전체 보기 기능 추가 (#439)
Browse files Browse the repository at this point in the history
  • Loading branch information
kseysh authored Nov 11, 2024
2 parents 0e81d18 + 2f35cd6 commit d345bfc
Show file tree
Hide file tree
Showing 15 changed files with 238 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.sopt.app.application.calendar;

import java.util.List;
import org.sopt.app.presentation.calendar.CalendarResponse;

public interface CalendarService {
List<CalendarResponse> getAllCurrentGenerationCalendar();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.sopt.app.application.calendar;

import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.sopt.app.domain.cache.CachedAllCalendarResponse;
import org.sopt.app.domain.cache.Calendars;
import org.sopt.app.interfaces.postgres.CalendarRepository;
import org.sopt.app.interfaces.postgres.redis.CachedCalendarRepository;
import org.sopt.app.presentation.calendar.CalendarResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class CalendarServiceImpl implements CalendarService {

private final CalendarRepository calendarRepository;
private final CachedCalendarRepository cachedCalendarRepository;

@Value("${sopt.current.generation}")
private Integer currentGeneration;

@Override
@Transactional
public List<CalendarResponse> getAllCurrentGenerationCalendar() {

Optional<CachedAllCalendarResponse> cachedCalendar = cachedCalendarRepository.findById(currentGeneration);

return cachedCalendar.orElseGet(this::cacheAllCalendarResponse)
.getCalendars().calendars().stream()
.map(CalendarResponse::of)
.toList();
}

private CachedAllCalendarResponse cacheAllCalendarResponse() {
return cachedCalendarRepository.save(
new CachedAllCalendarResponse(
currentGeneration,
new Calendars(calendarRepository.findAllByGenerationOrderByStartDate(currentGeneration))
)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@
import lombok.RequiredArgsConstructor;
import org.sopt.app.application.playground.PlaygroundClient;
import org.sopt.app.application.playground.dto.PlaygroundUserFindCondition;
import org.sopt.app.domain.entity.RecommendedUserIds;
import org.sopt.app.interfaces.postgres.RecommendedUserIdsRepository;
import org.sopt.app.domain.cache.CachedRecommendedUserIds;
import org.sopt.app.interfaces.postgres.redis.CachedRecommendedUserIdsRepository;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class PlaygroundUserFinderRedisImpl implements PlaygroundUserFinder {

private final PlaygroundClient playgroundClient;
private final RecommendedUserIdsRepository recommendedUserIdsRepository;
private final CachedRecommendedUserIdsRepository cachedRecommendedUserIdsRepository;

@Override
public Set<Long> findByCondition(PlaygroundUserFindCondition condition) {
String key = convertConditionToKey(condition);
Optional<RecommendedUserIds> recommendedUsers = recommendedUserIdsRepository.findById(key);
Optional<CachedRecommendedUserIds> recommendedUsers = cachedRecommendedUserIdsRepository.findById(key);

if (recommendedUsers.isPresent()) { // 캐싱 되어 있는 조건이라면 캐싱된 값을 반환
return recommendedUsers.get().getUserIds();
Expand All @@ -36,7 +36,7 @@ private String convertConditionToKey(PlaygroundUserFindCondition request) {
private Set<Long> cachingConditionAndResult(PlaygroundUserFindCondition condition) {
Map<String, String> headers = createAuthorizationHeaderByInternalPlaygroundToken();
Set<Long> playgroundUserIds = playgroundClient.getPlaygroundUserIdsByCondition(headers, condition).userIds();
recommendedUserIdsRepository.save(new RecommendedUserIds(condition, playgroundUserIds));
cachedRecommendedUserIdsRepository.save(new CachedRecommendedUserIds(condition, playgroundUserIds));
return playgroundUserIds;
}
}
26 changes: 24 additions & 2 deletions src/main/java/org/sopt/app/common/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package org.sopt.app.common.config;

import java.time.Duration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.cache.*;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.*;

@Configuration
@EnableRedisRepositories
Expand All @@ -27,4 +30,23 @@ public RedisConnectionFactory redisConnectionFactory() {
redisConfiguration.setPassword(password);
return new LettuceConnectionFactory(redisConfiguration);
}

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration cacheConfig =
RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer()
))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()
))
.entryTtl(Duration.ofMinutes(1L));

return RedisCacheManager
.RedisCacheManagerBuilder
.fromConnectionFactory(factory)
.cacheDefaults(cacheConfig)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.sopt.app.domain.cache;

import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

@Getter
@AllArgsConstructor(access = AccessLevel.PUBLIC)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@RedisHash(value = "allCalendar", timeToLive = 60 * 60 * 24 * 7L)
public class CachedAllCalendarResponse {

@Id
private Integer generation;

private Calendars calendars;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.sopt.app.domain.entity;
package org.sopt.app.domain.cache;

import java.util.Set;
import lombok.*;
Expand All @@ -10,14 +10,14 @@
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@RedisHash(value = "recommendedUsers", timeToLive = 60 * 60 * 24L)
public class RecommendedUserIds {
public class CachedRecommendedUserIds {

@Id
private String condition;

private Set<Long> userIds;

public RecommendedUserIds(final PlaygroundUserFindCondition request, final Set<Long> userIds) {
public CachedRecommendedUserIds(final PlaygroundUserFindCondition request, final Set<Long> userIds) {
this.condition = request.toString();
this.userIds = userIds;
}
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/sopt/app/domain/cache/Calendars.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.sopt.app.domain.cache;

import java.util.List;
import org.sopt.app.domain.entity.Calendar;

public record Calendars(
List<Calendar> calendars
) { }
41 changes: 41 additions & 0 deletions src/main/java/org/sopt/app/domain/entity/Calendar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.sopt.app.domain.entity;

import lombok.*;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import org.sopt.app.domain.enums.CalendarType;

@Getter
@Entity
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Calendar {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@NotNull
private Integer generation;

@NotNull
private String title;

@NotNull
private Boolean isOneDaySchedule;

@NotNull
private Boolean isOnlyActiveGeneration;

@NotNull
private LocalDate startDate;

@NotNull
private LocalDate endDate;

@NotNull
@Enumerated(EnumType.STRING)
private CalendarType type;
}
5 changes: 5 additions & 0 deletions src/main/java/org/sopt/app/domain/enums/CalendarType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.sopt.app.domain.enums;

public enum CalendarType {
EVENT, SEMINAR
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.sopt.app.interfaces.postgres;

import java.util.List;
import org.sopt.app.domain.entity.Calendar;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CalendarRepository extends JpaRepository<Calendar, Long> {

List<Calendar> findAllByGenerationOrderByStartDate(final Integer generation);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.sopt.app.interfaces.postgres.redis;

import org.sopt.app.domain.cache.CachedAllCalendarResponse;
import org.springframework.data.repository.CrudRepository;

public interface CachedCalendarRepository extends CrudRepository<CachedAllCalendarResponse, Integer> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.sopt.app.interfaces.postgres.redis;

import org.sopt.app.domain.cache.CachedRecommendedUserIds;
import org.springframework.data.repository.CrudRepository;

public interface CachedRecommendedUserIdsRepository extends CrudRepository<CachedRecommendedUserIds, String> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.sopt.app.presentation.calendar;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.sopt.app.application.calendar.CalendarService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v2/calendar")
public class CalendarController {

private final CalendarService calendarService;

@Operation(summary = "일정 전체 보기")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "success"),
@ApiResponse(responseCode = "500", description = "server error", content = @Content)
})
@GetMapping("/all")
public ResponseEntity<List<CalendarResponse>> getAllCalendar() {
return ResponseEntity.ok(
calendarService.getAllCurrentGenerationCalendar()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.sopt.app.presentation.calendar;

import java.time.LocalDate;
import lombok.*;
import org.sopt.app.domain.entity.Calendar;

@Getter
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class CalendarResponse {
private final String title;
private final LocalDate startDate;
private final LocalDate endDate;
private final Boolean isOneDaySchedule;
private final Boolean isOnlyActiveGeneration;

public static CalendarResponse of(Calendar calendar) {
return CalendarResponse.builder()
.startDate(calendar.getStartDate())
.endDate(calendar.getEndDate())
.title(calendar.getTitle())
.isOneDaySchedule(calendar.getIsOneDaySchedule())
.isOnlyActiveGeneration(calendar.getIsOnlyActiveGeneration())
.build();
}
}

0 comments on commit d345bfc

Please sign in to comment.