Skip to content

Commit

Permalink
refactor : 추천 프로젝트 api 리팩토링 #45
Browse files Browse the repository at this point in the history
  • Loading branch information
strangehoon committed Aug 7, 2024
1 parent abfe8d8 commit 56b76aa
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

import static com.sendback.global.common.ApiResponse.success;

@RestController
Expand Down Expand Up @@ -96,13 +94,9 @@ public ApiResponse<Object> deleteProject(

@GetMapping("/recommend")
public ApiResponse<List<RecommendedProjectResponseDto>> getRecommendedProject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.getPrincipal() == "anonymousUser") {
return success(projectService.getRecommendedProject(null));
}
Long userId = (Long) authentication.getPrincipal();
return success(projectService.getRecommendedProject(userId));
return success(projectService.getRecommendedProject());
}

@PutMapping("/{projectId}/pull-up")
public ApiResponse<PullUpProjectResponseDto> pullUpProject(
@UserId Long userId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.sendback.domain.project.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.sendback.domain.project.entity.Project;
import java.time.LocalDateTime;

Expand All @@ -11,19 +15,22 @@ public record RecommendedProjectResponseDto (
String title,
String summary,
String createdBy,

@JsonFormat(pattern = "yyyy.MM.dd")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
LocalDateTime createdAt,

String profileImageUrl
){
public static RecommendedProjectResponseDto of(Project project) {
return new RecommendedProjectResponseDto(
project.getId(),
project.getSummary(),
project.getUser().getNickname(),
project.getProgress().getValue(),
project.getFieldName().getName(),
project.getTitle(),
project.getSummary(),
project.getUser().getNickname(),
project.getCreatedAt(),
project.getUser().getProfileImageUrl()
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.sendback.domain.project.service;

import com.sendback.domain.field.entity.Field;
import com.sendback.domain.field.repository.FieldRepository;
import com.sendback.domain.like.repository.LikeRepository;
import com.sendback.domain.project.dto.request.SaveProjectRequestDto;
Expand All @@ -21,16 +20,17 @@
import com.sendback.global.common.CustomPage;
import com.sendback.global.common.constants.FieldName;
import com.sendback.global.config.image.service.ImageService;
import com.sendback.global.config.redis.RedisService;
import com.sendback.global.exception.type.BadRequestException;
import com.sendback.global.exception.type.NotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Optional;
Expand All @@ -49,6 +49,7 @@ public class ProjectService {
private final ScrapRepository scrapRepository;

private final FieldRepository fieldRepository;
private final RedisService redisService;

public ProjectDetailResponseDto getProjectDetail(Long userId, Long projectId) {
Project project = getProjectById(projectId);
Expand Down Expand Up @@ -115,23 +116,13 @@ public void deleteProject(Long userId, Long projectId) {
projectRepository.delete(project);
}

public List<RecommendedProjectResponseDto> getRecommendedProject(Long userId){
List<RecommendedProjectResponseDto> responseDtos = new ArrayList<>();
if(userId!=null) {
List<Field> fieldList = fieldRepository.findAllByUserId(userId);
List<FieldName> fieldNameList = fieldList.stream().map(Field::getName).collect(Collectors.toList());
List<Project> projects = projectRepository.findRecommendedProjects(fieldNameList, 12);
responseDtos.addAll(projects.stream().map(project -> RecommendedProjectResponseDto.of(project)).collect(Collectors.toList()));
if(projects.size()<12) {
List<Project> extraProject = projectRepository.findRecommendedProjects(12 - projects.size());
responseDtos.addAll(extraProject.stream().map(project -> RecommendedProjectResponseDto.of(project)).collect(Collectors.toList()));
}
}
else{
List<Project> projects = projectRepository.findRecommendedProjects(12);
responseDtos.addAll(projects.stream().map(project -> RecommendedProjectResponseDto.of(project)).collect(Collectors.toList()));
}
return responseDtos;

@Cacheable(value = "recommend", cacheManager = "redisCacheManager")
public List<RecommendedProjectResponseDto> getRecommendedProject() {
List<Project> projects = projectRepository.findRecommendedProjects(12);
return projects.stream()
.map(RecommendedProjectResponseDto::of)
.collect(Collectors.toList());
}

public void validateProjectAuthor(User user, Project project) {
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/sendback/global/common/BaseEntity.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.sendback.global.common;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
Expand All @@ -8,6 +12,7 @@
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.io.Serializable;
import java.time.LocalDateTime;

@EntityListeners(AuditingEntityListener.class)
Expand All @@ -17,10 +22,14 @@ public class BaseEntity {

@CreatedDate
@Column(updatable = false)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createdAt;

@LastModifiedDate
@Column(insertable = false)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime updatedAt;

}
Expand Down
54 changes: 53 additions & 1 deletion src/main/java/com/sendback/global/config/redis/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
package com.sendback.global.config.redis;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

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

@Configuration
@EnableRedisRepositories
public class RedisConfig {
Expand All @@ -21,6 +32,15 @@ public class RedisConfig {
@Value("${spring.data.redis.port}")
private int redisPort;


// jackson LocalDateTime mapper
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // timestamp 형식 안따르도록 설정
mapper.registerModules(new JavaTimeModule(), new Jdk8Module()); // LocalDateTime 매핑을 위해 모듈 활성화
return mapper;
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
Expand All @@ -33,8 +53,40 @@ public RedisTemplate<String, Object> redisTemplate() {
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper()));
return redisTemplate;
}

@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);

RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext
.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext
.SerializationPair
.fromSerializer(genericJackson2JsonRedisSerializer));

Map<String, RedisCacheConfiguration> cacheConfiguration = new HashMap<>();
cacheConfiguration.put("recommend", redisCacheConfiguration.entryTtl(Duration.ofSeconds(1800L)).disableCachingNullValues()
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())
)
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(genericJackson2JsonRedisSerializer)
));;


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

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@RequiredArgsConstructor
@Component
Expand All @@ -16,6 +16,14 @@ public class RedisService {

private final RedisTemplate<String, Object> redisTemplate;

public Object getValues(String key) {
return redisTemplate.opsForValue().get(key);
}

public void setValues(String key, Object data, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, data, timeout, unit);
}

public void put(Long userId, String refreshToken, Date expiredDate) {
Date now = new Date();
long expirationSeconds = (expiredDate.getTime() - now.getTime()) / 1000;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ public void getRecommended_success() throws Exception {
List<RecommendedProjectResponseDto> responseDtos = new ArrayList<>();
responseDtos.add(recommendedProjectResponseDto);

given(projectService.getRecommendedProject(anyLong())).willReturn(responseDtos);
given(projectService.getRecommendedProject()).willReturn(responseDtos);

// when
ResultActions resultActions = mockMvc.perform(get("/api/projects/recommend")
Expand Down Expand Up @@ -607,7 +607,7 @@ public void getRecommended_success() throws Exception {
fieldWithPath("data[].profileImageUrl").type(JsonFieldType.STRING)
.description("프로필 이미지")
)));
verify(projectService).getRecommendedProject(anyLong());
verify(projectService).getRecommendedProject();
}
}
@Nested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@
import com.sendback.domain.project.entity.Project;
import com.sendback.domain.user.entity.User;
import com.sendback.global.common.constants.FieldName;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

import static com.sendback.domain.user.fixture.UserFixture.mock_user;

public class ProjectFixture {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.sendback.domain.project.service;

import com.sendback.domain.field.entity.Field;
import com.sendback.domain.field.repository.FieldRepository;
import com.sendback.domain.like.repository.LikeRepository;
import com.sendback.domain.project.dto.request.SaveProjectRequestDto;
Expand All @@ -19,7 +18,6 @@
import com.sendback.domain.user.entity.User;
import com.sendback.domain.user.service.UserService;
import com.sendback.global.ServiceTest;
import com.sendback.global.common.constants.FieldName;
import com.sendback.global.common.CustomPage;
import com.sendback.global.config.image.service.ImageService;
import com.sendback.global.exception.type.BadRequestException;
Expand All @@ -38,14 +36,12 @@
import static com.sendback.domain.project.fixture.ProjectFixture.*;
import static com.sendback.domain.scrap.fixture.ScrapFixture.createDummyScrap;
import static com.sendback.domain.user.fixture.UserFixture.createDummyUser;
import static com.sendback.global.common.constants.FieldName.GAME;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;


public class ProjectServiceTest extends ServiceTest {

@InjectMocks
Expand Down Expand Up @@ -298,35 +294,19 @@ public void success() throws Exception {
class getRecommendedProject {

@Test
@Disabled
@DisplayName("성공하면 200과 함께 추천 프로젝트를 반환한다.")
void getUserInfo_success() {
// given
Long mock_userId = 1L;
List<FieldName> fieldNameList = List.of(GAME); // 수정된 부분
List<Field> fieldList = new ArrayList<>();
fieldList.add(Field.of(GAME, user));

List<RecommendedProjectResponseDto> projects = new ArrayList<>();
projects.add(MOCK_RECOMMEND_PROJECT_RESPONSE_DTO);

given(fieldRepository.findAllByUserId(mock_userId)).willReturn(fieldList);
//given(projectRepository.findRecommendedProjects(mock_userId, fieldNameList)).willReturn(projects);
List<Project> projects = Arrays.asList(project);
when(projectRepository.findRecommendedProjects(12)).thenReturn(projects);

// when
List<RecommendedProjectResponseDto> result = projectService.getRecommendedProject(mock_userId);
List<RecommendedProjectResponseDto> recommendedProjects = projectService.getRecommendedProject();

// then
assertThat(projects.get(0).projectId()).isEqualTo(result.get(0).projectId());
assertThat(projects.get(0).field()).isEqualTo(result.get(0).field());
assertThat(projects.get(0).createdAt()).isEqualTo(result.get(0).createdAt());
assertThat(projects.get(0).title()).isEqualTo(result.get(0).title());
assertThat(projects.get(0).profileImageUrl()).isEqualTo(result.get(0).profileImageUrl());
assertThat(projects.get(0).createdBy()).isEqualTo(result.get(0).createdBy());
assertThat(projects.get(0).summary()).isEqualTo(result.get(0).summary());
assertThat(projects.get(0).progress()).isEqualTo(result.get(0).progress());
assertThat(recommendedProjects).hasSize(1);
assertThat(recommendedProjects.get(0).title()).isEqualTo("기획중");
}

}

@DisplayName("프로젝트 끌올 시")
Expand Down

0 comments on commit 56b76aa

Please sign in to comment.