Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature/#240] 팝업 관련 api 구현 #241

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 93 additions & 14 deletions linkmind/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,28 +34,36 @@ dependencies {
implementation 'mysql:mysql-connector-java:8.0.32'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

runtimeOnly 'com.h2database:h2'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'org.springframework.boot:spring-boot-starter-validation'
testImplementation group: 'com.h2database', name: 'h2', version: '2.2.224'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'

implementation 'io.sentry:sentry-spring-boot-starter:5.7.0'
//JWT
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'
runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'org.springframework.boot:spring-boot-starter-validation'

implementation 'io.sentry:sentry-spring-boot-starter:5.7.0'
//JWT
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'


// FCM
implementation 'com.google.firebase:firebase-admin:9.1.1'
implementation 'com.squareup.okhttp3:okhttp:4.10.0' // Firebase 서버로 푸시 메시지 전송 시 필요

implementation 'org.springframework.boot:spring-boot-starter-actuator'

//Query DSL
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta"
annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
implementation 'org.springframework.boot:spring-boot-starter-actuator'

//Query DSL
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta"
annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

// S3 AWS
implementation("software.amazon.awssdk:bom:2.21.0")
Expand All @@ -72,8 +80,79 @@ dependencies {
implementation 'io.sentry:sentry-spring-boot-starter:5.7.0'

}
//sourceSets {
// main {
// java {
// srcDirs = ["$projectDir/src/main/java", "$projectDir/build/generated"]
// }
// }
//}
//compileJava { options.compilerArgs << '-parameters'}


tasks.named('test') {
useJUnitPlatform()
useJUnitPlatform()
// finalizedBy 'jacocoTestReport' // test가 끝나면 jacocoTestReport 동작
}
//// jacoco report 설정
//jacocoTestReport {
// reports {
// // html로 report 생성하기
// // 빌드경로/jacoco/report.html 폴더 내부로 경로 설정
// html.destination file("$jacoco/report.html")
// }
//
// // jacocoTestReport가 끝나면 jacocoTestCoverageVerification 동작
// finalizedBy 'jacocoTestCoverageVerification'
//}
//
//// jacoco 커버리지 검증 설정
//jacocoTestCoverageVerification {
// violationRules {
// rule {
// enabled = true // 커버리지 적용 여부
// element = 'CLASS' // 커버리지 적용 단위
//
// // 라인 커버리지 설정
// // 적용 대상 전체 소스 코드들을 한줄 한줄 따졌을 때 테스트 코드가 작성되어 있는 줄의 빈도
// // 테스트 코드가 작성되어 있는 비율이 90% 이상이어야 함
// limit {
// counter = 'LINE'
// value = 'COVEREDRATIO'
// minimum = 0.10
// }
//
// // 브랜치 커버리지 설정
// // if-else 등을 활용하여 발생되는 분기들 중 테스트 코드가 작성되어 있는 빈도
// // 테스트 코드가 작성되어 있는 비율이 90% 이상이어야 함
// limit {
// counter = 'BRANCH'
// value = 'COVEREDRATIO'
// minimum = 0.00
// }
//
// // 라인 최대 갯수 설정
// // 빈 줄을 제외하고 하나의 자바 파일에서 작성될 수 있는 최대 라인 갯수
// // 한 파일에 최대 500줄까지 작성되어야 함
// limit {
// counter = 'LINE'
// value = 'TOTALCOUNT'
// maximum = 500
// }
// // 커버리지 체크를 제외할 클래스들
// excludes = [
// // '*.test.*',
// '*.common.*',
// '*.config.*',
// '*.controller.*',
// '*.domain.*',
// '*.exception.*',
// '*.external.*',
// '*.infrastructure.*',
// '*.auth.*',
// '*.service.*'
//
// ]
// }
// }
//}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package com.app.toaster.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.querydsl.jpa.impl.JPAQueryFactory;

import jakarta.persistence.EntityManager;

@Configuration
public class JpaQueryFactoryConfig {

@Bean
JPAQueryFactory jpaQueryFactory(EntityManager em) {
return new JPAQueryFactory(em);
}
}
// package com.app.toaster.config;
//
// import org.springframework.context.annotation.Bean;
// import org.springframework.context.annotation.Configuration;
//
// import com.querydsl.jpa.impl.JPAQueryFactory;
//
// import jakarta.persistence.EntityManager;
//
// @Configuration
// public class JpaQueryFactoryConfig {
//
// @Bean
// JPAQueryFactory jpaQueryFactory(EntityManager em) {
// return new JPAQueryFactory(em);
// }
// }
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum Error {
NOT_FOUND_IMAGE_EXCEPTION(HttpStatus.NOT_FOUND, "s3 서비스에서 이미지를 찾을 수 없습니다."),
NOT_FOUND_TOAST_FILTER(HttpStatus.NOT_FOUND, "유효하지 않은 필터입니다."),
NOT_FOUND_TIMER(HttpStatus.NOT_FOUND, "찾을 수 없는 타이머입니다."),
NOT_FOUND_POPUP_EXCEPTION(HttpStatus.NOT_FOUND, "유효하지 않은 팝업입니다."),

/**
* 400 BAD REQUEST EXCEPTION
Expand Down
2 changes: 2 additions & 0 deletions linkmind/src/main/java/com/app/toaster/exception/Success.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public enum Success {
GET_LINKS_SUCCESS(HttpStatus.OK, "이주의 링크 조회 성공"),
GET_SITES_SUCCESS(HttpStatus.OK, "추천 사이트 조회 성공"),
GET_SETTINGS_SUCCESS(HttpStatus.OK, "설정 페이지 조회 성공"),
GET_POPUP_SUCCESS(HttpStatus.OK, "팝업 정보 조회 성공"),

GET_CATEORIES_SUCCESS(HttpStatus.OK, "전체 카테고리 조회 성공"),
GET_CATEORY_SUCCESS(HttpStatus.OK, "세부 카테고리 조회 성공"),
Expand Down Expand Up @@ -52,6 +53,7 @@ public enum Success {
PUSH_ALARM_PERIODIC_SUCCESS(HttpStatus.OK, "푸시알림 활성에 성공했습니다."),
PUSH_ALARM_SUCCESS(HttpStatus.OK, "푸시알림 전송에 성공했습니다."),
CLEAR_SCHEDULED_TASKS_SUCCESS(HttpStatus.OK, "스케줄러에서 예약된 작업을 제거했습니다."),
UPDATE_POPUP_SUCCESS(HttpStatus.OK, "팝업 데이터 수정 성공"),


/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// package com.app.toaster.infrastructure.querydsl;
//
// import static com.app.toaster.domain.QToast.*;
// import static com.app.toaster.domain.QUser.*;
//
// import java.time.LocalDateTime;
// import java.util.ArrayList;
// import java.util.List;
// import org.springframework.data.jpa.repository.Modifying;
// import org.springframework.data.jpa.repository.Query;
// import org.springframework.data.repository.query.Param;
// import org.springframework.stereotype.Repository;
//
// import com.app.toaster.domain.Category;
// import com.app.toaster.domain.QToast;
// import com.app.toaster.domain.QUser;
// import com.app.toaster.domain.Toast;
// import com.app.toaster.domain.User;
// import com.querydsl.core.types.Path;
// import com.querydsl.core.types.dsl.BooleanExpression;
// import com.querydsl.jpa.impl.JPAQueryFactory;
//
// import jakarta.persistence.EntityManager;
// import lombok.RequiredArgsConstructor;
//
// @Repository
// @RequiredArgsConstructor
// public class CustomToastRepository {
// private final JPAQueryFactory queryFactory;
// // private final EntityManager em;
// public List<Toast> getAllByCategory(Category category){
// return queryFactory.select(toast)
// .from(toast)
// .where(eqCategoryId(category.getCategoryId()))
// .fetch();
// }
//
// private BooleanExpression eqCategoryId(Long id){
// if (id == null){
// return null;
// }
// return toast.category.categoryId.eq(id);
// }
//
// ArrayList<Toast> findByIsReadAndCategory(Boolean isRead, Category category){
// return null;
// };
//
// ArrayList<Toast> getAllByUser(User user){
// return null;
// }
//
// List<Toast> getAllByUserOrderByCreatedAtDesc(User user){
// return null;
// }
//
//
// ArrayList<Toast> getAllByUserAndIsReadIsTrue(User user){
// return null;
// }
//
//
// // @Modifying
// // @Query("UPDATE Toast t SET t.category = null WHERE t.category.categoryId IN :categoryIds")
// // void updateCategoryIdsToNull(@Param("categoryIds") List<Long> categoryIds){
// // }
//
// //querydsl의 수정은 bulkupdate라 영속성컨텍스트를 안거치기 때문에 무조건 flush,clear해주자.
// void updateCategoryIdsToNull(List<Long> categoryIds){
// queryFactory.update(toast)
// .set(toast.category, (Category)null)
// .where(toast.category.categoryId.in(categoryIds))
// .execute();
// // em.flush(); //test 코드에서는 따로 em을 주입하는 중이므로 테스트 후 넣자.
// // em.clear();
// }
//
//
// List<Toast> searchToastsByQuery(Long userId, String query){
// return queryFactory.select(toast)
// .from(toast)
// .leftJoin(toast.user, user).fetchJoin()
// .where(eqToastOwner(userId), containToastTitle(query))
// .fetch();
// }
// private BooleanExpression eqToastOwner(Long userId){
// return userId != null?toast.user.userId.eq(userId):null;
// }
//
// private BooleanExpression containToastTitle(String query){
// if (query == null || query.isEmpty() || query.isBlank()){
// return null;
// }
// return toast.title.containsIgnoreCase(query);
// }
//
//
//
// Long countAllByUser(User user){
// return null;
// }
//
//
// Long countALLByUserAndIsReadTrue(User user){
// return null;
// }
//
//
// Long countALLByUserAndIsReadFalse(User user){
// return null;
// }
//
//
// Long countAllByCategory(Category category){
// return null;
// }
//
//
// Long countAllByCategoryAndIsReadTrue(Category category){
// return null;
// }
//
//
// Long countAllByCategoryAndIsReadFalse(Category category){
// return null;
// }
//
//
// Integer getUnReadToastNumber(Long userId){
//
// Integer count = queryFactory.select(toast.count().intValue())
// .from(toast)
// .where(eqToastOwner(userId).and(toast.isRead.isFalse()))
// .fetchOne();
// return (count!=null)?count:0;
// }
//
//
// @Query("SELECT COUNT(t) FROM Toast t WHERE t.user=:user AND t.createdAt >= :startOfWeek AND t.createdAt <= :endOfWeek")
// Long countAllByCreatedAtThisWeek(@Param("startOfWeek") LocalDateTime startOfWeek,
// @Param("endOfWeek") LocalDateTime endOfWeek, @Param("user") User user){
// return null;
// }
//
//
// @Query("SELECT COUNT(t) FROM Toast t WHERE t.user=:user AND t.isRead = true AND t.updateAt >= :startOfWeek AND t.updateAt <= :endOfWeek")
// Long countAllByUpdateAtThisWeek(@Param("startOfWeek") LocalDateTime startOfWeek,
// @Param("endOfWeek") LocalDateTime endOfWeek,
// @Param("user") User user){
// return null;
// }
//
//
// }
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.app.toaster.popup.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.app.toaster.popup.controller.request.PopUpRequestDto;
import com.app.toaster.popup.controller.response.InvisibleResponseDto;
import com.app.toaster.popup.controller.response.PopupResponseDto;
import com.app.toaster.popup.service.PopupService;
import com.app.toaster.common.dto.ApiResponse;
import com.app.toaster.config.UserId;
import com.app.toaster.exception.Success;

import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v2/popup")
public class PopupController {
private final PopupService popupService;


@PatchMapping
public ApiResponse<InvisibleResponseDto> updateInvisible(@UserId Long userId, @RequestBody PopUpRequestDto popUpRequestDto){
return ApiResponse.success(Success.UPDATE_POPUP_SUCCESS, popupService.updatePopupInvisible(userId,popUpRequestDto));
Copy link
Member

@mmihye mmihye Sep 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RequestDto를 그대로 service에 넘겨주면 controller과 service가 분리되기보다 의존성이 높아진다고 controller -> service에 넘길때도 따로 dto를 만든다고해서 이런식으로 리펙토링 진행해보려고하는데 어떠신가요??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 이해했습니다. 좋은 생각인 것 같아요.! 덕분에 저도 요런 링크를 찾았습니당

https://ksh-coding.tistory.com/102

약간 이해한대로 요약해보면 현재 저희는 여기서 설명하는 1번 방법인
service레이어에서 requestDto를 컨트롤러에서 받은 그대로 사용하고 있는데, 2번 방법인 requestCommandDto라는 service레이어에서 사용할 때 service와 controller의 결합을 약하게 만들 수 있다는 것이라고 이해했습니다!

혹시 그러면 이 리팩토링을 어느정도 사용하시는 걸 생각하고 계시나욤? 일단 리팩토링을 적용하는 것이 저도 좋다고 생각하는데
전체적으로 사용한다고 하면 dto 관리측면이 많이 늘어날 수 있다는 단점이 있을 수 있을 것 같습니당

위 링크의 필자분은 처음에 들어온 requestDto와 컨트롤러에서 외부 api등을 호출해야해서 service와 controller레이어에서 원하는 requestDto 포멧이 바뀌는 경우에만 적용하는 것이 좋아보인다고 해요!

물론 의존성을 분리한다는 장점이 있는 것은 맞지만 단점과 비교했을 때 많은 이득을 볼 수 있을지 사실 체감이 잘 되지는 않는 것 같아서 여쭤봅니당..! (근데 시도 자체는 좋은 것 같아서 좋은 고민인 것 같아요!☺️ )

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 일단 기존꺼보다 새로운거 개발할때 적용해보려고 생각했는데 기존꺼는 어떻게 해야할지 잘 모르겠네용...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존꺼 리팩토링.. 어디부터 손대야할지 빡세긴하네용 ㅜ

}

@GetMapping
public ApiResponse<PopupResponseDto> getPopUpInformation(@UserId Long userId){
return ApiResponse.success(Success.GET_POPUP_SUCCESS, popupService.findPopupInformation(userId));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.app.toaster.popup.controller.request;

import jakarta.validation.Valid;

public record PopUpRequestDto(
@Valid
Long popupId,
@Valid
boolean invisible
) {
}
Loading
Loading