Skip to content

Commit

Permalink
Merge pull request #81 from Team-UMC/feature/#80/project_like
Browse files Browse the repository at this point in the history
[FEAT] ํ”„๋กœ์ ํŠธ ์ข‹์•„์š” API ์ถ”๊ฐ€
  • Loading branch information
junseokkim authored Feb 18, 2024
2 parents dda4cec + 54db55c commit a5dd435
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.umc.networkingService.domain.project.controller;

import com.umc.networkingService.config.security.auth.CurrentMember;
import com.umc.networkingService.domain.member.entity.Member;
import com.umc.networkingService.domain.project.dto.response.ProjectAllResponse;
import com.umc.networkingService.domain.project.dto.response.ProjectDetailResponse;
import com.umc.networkingService.domain.project.dto.response.ProjectLikeResponse;
import com.umc.networkingService.domain.project.entity.ProjectType;
import com.umc.networkingService.domain.project.service.ProjectService;
import com.umc.networkingService.global.common.base.BaseResponse;
Expand Down Expand Up @@ -38,11 +41,13 @@ public class ProjectController {
@Parameter(name = "size", description = "ํ•œ ํŽ˜์ด์ง€์— ์กฐํšŒ๋˜๋Š” ํ”„๋กœ์ ํŠธ ์ˆ˜์ž…๋‹ˆ๋‹ค,(๋ฏธ์ž…๋ ฅ ์‹œ ๊ธฐ๋ณธ 10๊ฐœ)"),
})
@GetMapping
public BaseResponse<ProjectAllResponse> inquiryProjects(@RequestParam(required = false) Semester semester,
@RequestParam(required = false) ProjectType type,
@PageableDefault(sort = "name", direction = Sort.Direction.ASC)
@Parameter(hidden = true) Pageable pageable) {
return BaseResponse.onSuccess(projectService.inquiryProjects(semester, type, pageable));
public BaseResponse<ProjectAllResponse> inquiryProjects(
@CurrentMember Member member,
@RequestParam(required = false) Semester semester,
@RequestParam(required = false) ProjectType type,
@PageableDefault(sort = "name", direction = Sort.Direction.ASC)
@Parameter(hidden = true) Pageable pageable) {
return BaseResponse.onSuccess(projectService.inquiryProjects(member, semester, type, pageable));
}

@Operation(summary = "HOT ํ”„๋กœ์ ํŠธ ์กฐํšŒ API", description = "์กฐํšŒ์ˆ˜ ๊ธฐ์ค€์œผ๋กœ HOT ํ”„๋กœ์ ํŠธ๋ฅผ ์กฐํšŒํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.")
Expand All @@ -54,9 +59,11 @@ public BaseResponse<ProjectAllResponse> inquiryProjects(@RequestParam(required =
@Parameter(name = "size", description = "ํ•œ ํŽ˜์ด์ง€์— ์กฐํšŒ๋˜๋Š” ํ”„๋กœ์ ํŠธ ์ˆ˜์ž…๋‹ˆ๋‹ค,(๋ฏธ์ž…๋ ฅ ์‹œ ๊ธฐ๋ณธ 10๊ฐœ)"),
})
@GetMapping("/hot")
public BaseResponse<ProjectAllResponse> inquiryHotProjects(@PageableDefault(sort = "hitCount", direction = Sort.Direction.DESC)
@Parameter(hidden = true) Pageable pageable) {
return BaseResponse.onSuccess(projectService.inquiryHotProjects(pageable));
public BaseResponse<ProjectAllResponse> inquiryHotProjects(
@CurrentMember Member member,
@PageableDefault(sort = "hitCount", direction = Sort.Direction.DESC)
@Parameter(hidden = true) Pageable pageable) {
return BaseResponse.onSuccess(projectService.inquiryHotProjects(member, pageable));
}

@Operation(summary = "ํ”„๋กœ์ ํŠธ ๊ฒ€์ƒ‰ API", description = "ํ‚ค์›Œ๋“œ๊ฐ€ ํ”„๋กœ์ ํŠธ๋ช… ๋˜๋Š” ํƒœ๊ทธ์— ํฌํ•จ๋œ ํ”„๋กœ์ ํŠธ ๋ชฉ๋ก์„ ๊ฒ€์ƒ‰ํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.")
Expand All @@ -69,10 +76,12 @@ public BaseResponse<ProjectAllResponse> inquiryHotProjects(@PageableDefault(sort
@Parameter(name = "size", description = "ํ•œ ํŽ˜์ด์ง€์— ์กฐํšŒ๋˜๋Š” ํ”„๋กœ์ ํŠธ ์ˆ˜์ž…๋‹ˆ๋‹ค,(๋ฏธ์ž…๋ ฅ ์‹œ ๊ธฐ๋ณธ 10๊ฐœ)"),
})
@GetMapping("/search")
public BaseResponse<ProjectAllResponse> searchProject(@RequestParam String keyword,
@PageableDefault(sort = "name", direction = Sort.Direction.ASC)
@Parameter(hidden = true) Pageable pageable) {
return BaseResponse.onSuccess(projectService.searchProject(keyword, pageable));
public BaseResponse<ProjectAllResponse> searchProject(
@CurrentMember Member member,
@RequestParam String keyword,
@PageableDefault(sort = "name", direction = Sort.Direction.ASC)
@Parameter(hidden = true) Pageable pageable) {
return BaseResponse.onSuccess(projectService.searchProject(member ,keyword, pageable));
}

@Operation(summary = "ํ”„๋กœ์ ํŠธ ์ƒ์„ธ ์กฐํšŒ API", description = "ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ธ ์กฐํšŒํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.")
Expand All @@ -81,8 +90,23 @@ public BaseResponse<ProjectAllResponse> searchProject(@RequestParam String keywo
@ApiResponse(responseCode = "PROJECT001", description = "์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ”„๋กœ์ ํŠธ ์ž…๋‹ˆ๋‹ค.")
})
@GetMapping("/{projectId}")
public BaseResponse<ProjectDetailResponse> inquiryProjectDetail(@PathVariable ("projectId") UUID projectId){
return BaseResponse.onSuccess(projectService.inquiryProjectDetail(projectId));
public BaseResponse<ProjectDetailResponse> inquiryProjectDetail(
@CurrentMember Member member,
@PathVariable ("projectId") UUID projectId){
return BaseResponse.onSuccess(projectService.inquiryProjectDetail(member, projectId));
}

@Operation(summary = "ํ”„๋กœ์ ํŠธ ์ข‹์•„์š”/์ทจ์†Œ API", description = "ํ”„๋กœ์ ํŠธ์— ์ข‹์•„์š”๋ฅผ ๋ˆ„๋ฅด๋Š” API์ž…๋‹ˆ๋‹ค.")
@ApiResponses(value = {
@ApiResponse(responseCode = "COMMON200", description = "์„ฑ๊ณต"),
@ApiResponse(responseCode = "PROJECT001", description = "์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ”„๋กœ์ ํŠธ ์ž…๋‹ˆ๋‹ค.")
})
@PostMapping("/{projectId}/like")
public BaseResponse<ProjectLikeResponse> likeProject(
@CurrentMember Member member,
@PathVariable ("projectId") UUID projectId
){
return BaseResponse.onSuccess(projectService.likeProject(member, projectId));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ public static class ProjectInfo {
private Semester semester;
private List<ProjectType> types;
private List<String> tags;
private Boolean isLike;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class ProjectDetailResponse {
private String description;
private String logoImage;
private Semester semester;
private boolean isLike;
private List<ProjectType> types;
private List<String> tags;
private List<ProjectMemberInfo> projectMembers;
Expand All @@ -31,5 +32,6 @@ public static class ProjectMemberInfo {
private Part part;
private String nickname;
private String name;
private Boolean isLike;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.umc.networkingService.domain.project.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ProjectLikeResponse{
private int likeCount;
private Boolean isLike;
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ public class Project extends BaseEntity {
@ColumnDefault("0")
private Long hitCount;

@ColumnDefault("0")
private int heartCount; //์ข‹์•„์š” ์ˆ˜

public void updateProject(ProjectUpdateRequest request){
this.name = request.getName();
this.description = request.getDescription();
Expand All @@ -64,4 +67,12 @@ public void deleteProjectImage() {
public void addHitCount() {
this.hitCount += 1L;
}

public void addHeartCount() {
this.heartCount += 1;
}

public void subtractHeartCount() {
this.heartCount -= 1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.umc.networkingService.domain.project.entity;

import com.umc.networkingService.domain.member.entity.Member;
import com.umc.networkingService.global.common.base.BaseEntity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLRestriction;
import org.hibernate.annotations.UuidGenerator;

import java.util.UUID;

@Getter
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@SQLRestriction("deleted_at is null")
public class ProjectHeart extends BaseEntity {

@Id
@UuidGenerator
@Column(name="project_heart_id")
private UUID id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(nullable = false, name="project_id")
private Project project;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(nullable = false, name="member_id")
private Member member;

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public ProjectMember toProjectMember(ProjectCreateRequest.ProjectMemberInfo memb
.build();
}

public ProjectAllResponse.ProjectInfo toProjectInfo(Project project) {
public ProjectAllResponse.ProjectInfo toProjectInfo(Project project, boolean isLike) {
return ProjectAllResponse.ProjectInfo.builder()
.projectId(project.getId())
.name(project.getName())
Expand All @@ -41,10 +41,11 @@ public ProjectAllResponse.ProjectInfo toProjectInfo(Project project) {
.semester(project.getSemester())
.types(project.getTypes())
.tags(project.getTags())
.isLike(isLike)
.build();
}

public ProjectDetailResponse toProjectDetailResponse(Project project, List<ProjectMember> projectMembers) {
public ProjectDetailResponse toProjectDetailResponse(Project project, List<ProjectMember> projectMembers, boolean isLike) {
return ProjectDetailResponse.builder()
.name(project.getName())
.description(project.getDescription())
Expand All @@ -53,6 +54,7 @@ public ProjectDetailResponse toProjectDetailResponse(Project project, List<Proje
.types(project.getTypes())
.tags(project.getTags())
.projectMembers(toProjectMemberInfos(projectMembers))
.isLike(isLike)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.umc.networkingService.domain.project.repository;

import com.umc.networkingService.domain.project.entity.ProjectHeart;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.UUID;

public interface ProjectHeartRepository extends JpaRepository<ProjectHeart, UUID> {
boolean existsByMemberIdAndProjectId(UUID memberId, UUID projectId);
void deleteByMemberIdAndProjectId(UUID memberId, UUID projectId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.umc.networkingService.domain.project.dto.response.ProjectAllResponse;
import com.umc.networkingService.domain.project.dto.response.ProjectDetailResponse;
import com.umc.networkingService.domain.project.dto.response.ProjectIdResponse;
import com.umc.networkingService.domain.project.dto.response.ProjectLikeResponse;
import com.umc.networkingService.domain.project.entity.Project;
import com.umc.networkingService.domain.project.entity.ProjectType;
import com.umc.networkingService.global.common.base.EntityLoader;
Expand All @@ -19,8 +20,11 @@ public interface ProjectService extends EntityLoader<Project, UUID> {
ProjectIdResponse createProject(Member member, MultipartFile projectImage, ProjectCreateRequest request);
ProjectIdResponse updateProject(Member member,UUID projectId, ProjectUpdateRequest request);
ProjectIdResponse deleteProject(UUID projectId);
ProjectAllResponse inquiryProjects(Semester semester, ProjectType type, Pageable pageable);
ProjectAllResponse inquiryHotProjects(Pageable pageable);
ProjectAllResponse searchProject(String keyword, Pageable pageable);
ProjectDetailResponse inquiryProjectDetail(UUID projectId);
ProjectAllResponse inquiryProjects(Member member, Semester semester, ProjectType type, Pageable pageable);
ProjectAllResponse inquiryHotProjects(Member member, Pageable pageable);
ProjectAllResponse searchProject(Member member, String keyword, Pageable pageable);
ProjectDetailResponse inquiryProjectDetail(Member member, UUID projectId);

ProjectLikeResponse likeProject(Member member, UUID projectId);
boolean isLikeProject(Member member, UUID projectId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
import com.umc.networkingService.domain.project.dto.response.ProjectAllResponse;
import com.umc.networkingService.domain.project.dto.response.ProjectDetailResponse;
import com.umc.networkingService.domain.project.dto.response.ProjectIdResponse;
import com.umc.networkingService.domain.project.dto.response.ProjectLikeResponse;
import com.umc.networkingService.domain.project.entity.Project;
import com.umc.networkingService.domain.project.entity.ProjectHeart;
import com.umc.networkingService.domain.project.entity.ProjectMember;
import com.umc.networkingService.domain.project.entity.ProjectType;
import com.umc.networkingService.domain.project.mapper.ProjectMapper;
import com.umc.networkingService.domain.project.repository.ProjectHeartRepository;
import com.umc.networkingService.domain.project.repository.ProjectMemberRepository;
import com.umc.networkingService.domain.project.repository.ProjectRepository;
import com.umc.networkingService.global.common.enums.Semester;
Expand All @@ -23,9 +26,11 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
Expand All @@ -36,6 +41,8 @@ public class ProjectServiceImpl implements ProjectService{
private final ProjectMapper projectMapper;
private final ProjectRepository projectRepository;
private final ProjectMemberRepository projectMemberRepository;
private final ProjectHeartRepository projectHeartRepository;


@Override
public ProjectIdResponse createProject(Member member, MultipartFile projectImage, ProjectCreateRequest request) {
Expand Down Expand Up @@ -89,7 +96,7 @@ public ProjectIdResponse deleteProject(UUID projectId){
}

@Override
public ProjectAllResponse inquiryProjects(Semester semester, ProjectType type, Pageable pageable) {
public ProjectAllResponse inquiryProjects(Member member, Semester semester, ProjectType type, Pageable pageable) {
Page<Project> projects;

// ๊ธฐ์ˆ˜ ์กฐ๊ฑด๊ณผ ํƒ€์ž… ์กฐ๊ฑด์˜ ์œ ๋ฌด์— ๋”ฐ๋ผ์„œ ์กฐํšŒ
Expand All @@ -104,46 +111,93 @@ public ProjectAllResponse inquiryProjects(Semester semester, ProjectType type, P
}

return new ProjectAllResponse(
projects.stream().map(projectMapper::toProjectInfo).toList(),
projects.stream().map(
project -> projectMapper.toProjectInfo(project, isLikeProject(member, project.getId()))
).toList(),
projects.hasNext()
);
}

@Override
public ProjectAllResponse inquiryHotProjects(Pageable pageable) {
Page<Project> projects = projectRepository.findAll(pageable);
public ProjectAllResponse inquiryHotProjects(Member member, Pageable pageable) {
// ์กฐํšŒ์ˆ˜ 1์ , ํ•˜ํŠธ์ˆ˜ 3์ ์œผ๋กœ ์ ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•ด ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌ
List<Project> projects = projectRepository.findAll();
List<Project> hotProjects = projects.stream()
.sorted(Comparator.comparingLong(this::calculateScore).reversed()) //์ ์ˆ˜ ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌ
.skip(pageable.getOffset()) // page * size ๋งŒํผ skip
.limit(pageable.getPageSize()) // size ๋งŒํผ limit
.toList();


return ProjectAllResponse.builder()
.projects(hotProjects.stream().map(
project -> projectMapper.toProjectInfo(project, isLikeProject(member, project.getId()))
).toList())
.build();
}

return new ProjectAllResponse(
projects.stream().map(projectMapper::toProjectInfo).toList(),
projects.hasNext()
);
private Long calculateScore(Project project) { // ์กฐํšŒ์ˆ˜ 1์ , ํ•˜ํŠธ์ˆ˜ 3์ ์œผ๋กœ ์ ์ˆ˜๋ฅผ ๊ณ„์‚ฐ
return project.getHitCount() + (project.getHeartCount() * 3L);
}

@Override
public ProjectAllResponse searchProject(String keyword, Pageable pageable){
public ProjectAllResponse searchProject(Member member ,String keyword, Pageable pageable){
Page<Project> projects = projectRepository.findByNameContainsOrTagContains(keyword, pageable);

return new ProjectAllResponse(
projects.stream().map(projectMapper::toProjectInfo).toList(),
projects.stream().map(
project -> projectMapper.toProjectInfo(project, isLikeProject(member, project.getId()))
).toList(),
projects.hasNext()
);
}

@Override
@Transactional
public ProjectDetailResponse inquiryProjectDetail(UUID projectId){
public ProjectDetailResponse inquiryProjectDetail(Member member , UUID projectId){
Project project = loadEntity(projectId);
// ์กฐํšŒ์ˆ˜ ์ฆ๊ฐ€
project.addHitCount();

List<ProjectMember> projectMembers = projectMemberRepository.findAllByProject(project);

return projectMapper.toProjectDetailResponse(project, projectMembers);
return projectMapper.toProjectDetailResponse(project, projectMembers, isLikeProject(member, projectId));
}

@Override
public Project loadEntity(UUID id) {
return projectRepository.findById(id)
.orElseThrow(() -> new RestApiException(ProjectErrorCode.EMPTY_PROJECT));
}

@Override
@Transactional
public ProjectLikeResponse likeProject(Member member, UUID projectId) {
Project project = loadEntity(projectId);
if(projectHeartRepository.existsByMemberIdAndProjectId(member.getId(),projectId)){ //์ด๋ฏธ ํ•˜ํŠธ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ
projectHeartRepository.deleteByMemberIdAndProjectId(member.getId(),projectId); //ํ•˜ํŠธ ์—”ํ‹ฐํ‹ฐ ์‚ญ์ œ
project.subtractHeartCount();
return ProjectLikeResponse.builder()
.likeCount(project.getHeartCount())
.isLike(false).build();

} else{
projectHeartRepository.save( //ํ•˜ํŠธ ์—”ํ‹ฐํ‹ฐ ์ƒ์„ฑ
ProjectHeart.builder()
.member(member)
.project(project)
.build()
);
project.addHeartCount();
return ProjectLikeResponse.builder()
.likeCount(project.getHeartCount())
.isLike(true).build();
}
}

@Override
@Transactional(readOnly = true)
public boolean isLikeProject(Member member, UUID projectId) {
return projectHeartRepository.existsByMemberIdAndProjectId(member.getId(),projectId);
}
}

0 comments on commit a5dd435

Please sign in to comment.