From 69264f35f3d7cc46ce1c0352654d812646f6f467 Mon Sep 17 00:00:00 2001 From: jiseon Date: Mon, 11 Nov 2024 17:13:03 +0900 Subject: [PATCH 1/9] =?UTF-8?q?ITDS-60=20feat:=20MariaDB=20custom=20dialec?= =?UTF-8?q?t=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../itit/config/MariaDBFullTextDialect.java | 24 +++++++++++++++++++ src/main/resources/application.yml | 1 + 2 files changed, 25 insertions(+) create mode 100644 src/main/java/com/dissonance/itit/config/MariaDBFullTextDialect.java diff --git a/src/main/java/com/dissonance/itit/config/MariaDBFullTextDialect.java b/src/main/java/com/dissonance/itit/config/MariaDBFullTextDialect.java new file mode 100644 index 0000000..451219e --- /dev/null +++ b/src/main/java/com/dissonance/itit/config/MariaDBFullTextDialect.java @@ -0,0 +1,24 @@ +package com.dissonance.itit.config; + +import org.hibernate.boot.model.FunctionContributions; +import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.function.CommonFunctionFactory; +import org.hibernate.type.StandardBasicTypes; + +public class MariaDBFullTextDialect extends MariaDBDialect { + @Override + public void initializeFunctionRegistry(FunctionContributions functionContributions) { + super.initializeFunctionRegistry(functionContributions); + + CommonFunctionFactory commonFunctionFactory = new CommonFunctionFactory(functionContributions); + commonFunctionFactory.windowFunctions(); + commonFunctionFactory.hypotheticalOrderedSetAggregates_windowEmulation(); + + // Full-Text Search 관련 함수 등록 + functionContributions.getFunctionRegistry().registerPattern( + "match_against", + "MATCH(?1, ?2) AGAINST (?3 IN BOOLEAN MODE)", + functionContributions.getTypeConfiguration().getBasicTypeRegistry().resolve(StandardBasicTypes.BOOLEAN) + ); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index af309a6..af18691 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -22,6 +22,7 @@ spring: properties: hibernate: format_sql: true + dialect: com.dissonance.itit.config.MariaDBFullTextDialect defer-datasource-initialization: true profiles: include: oauth From 120d405d3b188affe80ec883902eb4427ee76ac8 Mon Sep 17 00:00:00 2001 From: jiseon Date: Mon, 11 Nov 2024 17:21:08 +0900 Subject: [PATCH 2/9] =?UTF-8?q?ITDS-60=20feat:=20=EC=A0=9C=EB=AA=A9+?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EA=B2=80=EC=83=89=20full=20text=20search?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../itit/controller/InfoPostController.java | 13 +++- .../repository/InfoPostRepositorySupport.java | 60 +++++++++++++++---- .../itit/service/InfoPostService.java | 8 ++- .../itit/service/InfoPostServiceTest.java | 4 +- 4 files changed, 70 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/dissonance/itit/controller/InfoPostController.java b/src/main/java/com/dissonance/itit/controller/InfoPostController.java index a1d7fd9..e8fc1b2 100644 --- a/src/main/java/com/dissonance/itit/controller/InfoPostController.java +++ b/src/main/java/com/dissonance/itit/controller/InfoPostController.java @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.dissonance.itit.common.annotation.CurrentUser; @@ -42,10 +43,18 @@ public ApiResponse reportedInfoPost(@PathVariable Long infoPostId, @Curr @GetMapping("/categories/{categoryId}/posts") @Operation(summary = "공고 게시글 목록 조회", description = "카테고리별 공고 게시글 목록을 조회합니다. (정렬: 최신순 - latest, 마감일순 - deadline)") - public ApiResponse> getInfoPostsByCategory(@PathVariable Integer categoryId, - Pageable pageable) { + public ApiResponse> getInfoPostsByCategory(@PathVariable Integer categoryId, Pageable pageable) { Page infoPostRes = infoPostService.getInfoPostsByCategoryId(categoryId, pageable); return ApiResponse.success(infoPostRes); } + + @GetMapping("/search") + @Operation(summary = "공고 게시글 키워드 검색", description = "공고 게시글을 키워드로 검색합니다.") + public ApiResponse> getInfoPostsByKeyword(@RequestParam String keyword, + Pageable pageable) { + Page infoPostRes = infoPostService.getInfoPostsByKeyword(keyword, pageable); + + return ApiResponse.success(infoPostRes); + } } diff --git a/src/main/java/com/dissonance/itit/repository/InfoPostRepositorySupport.java b/src/main/java/com/dissonance/itit/repository/InfoPostRepositorySupport.java index 9cf9dfa..596ca00 100644 --- a/src/main/java/com/dissonance/itit/repository/InfoPostRepositorySupport.java +++ b/src/main/java/com/dissonance/itit/repository/InfoPostRepositorySupport.java @@ -18,6 +18,7 @@ import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.BooleanTemplate; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -67,16 +68,16 @@ public Page findInfoPostsByCategoryId(Integer categoryId, Pageable infoPost.title, infoPost.recruitmentEndDate)) .from(infoPost) - .where(createCondition(categoryId)) + .where(buildCategoryCondition(categoryId)) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) - .orderBy(getOrderSpecifiers(pageable.getSort())) + .orderBy(buildOrderSpecifiers(pageable.getSort())) .fetch(); - return paginationInfoPosts(categoryId, InfoPostRes.of(postInfos), pageable); + return paginateInfoPostsByCategory(categoryId, InfoPostRes.of(postInfos), pageable); } - public OrderSpecifier[] getOrderSpecifiers(Sort sort) { + public OrderSpecifier[] buildOrderSpecifiers(Sort sort) { List> orderSpecifiers = new ArrayList<>(); for (Sort.Order order : sort) { @@ -115,20 +116,20 @@ public OrderSpecifier[] getOrderSpecifiers(Sort sort) { } } - private Page paginationInfoPosts(Integer categoryId, List infoPostRes, + private Page paginateInfoPostsByCategory(Integer categoryId, List infoPostRes, Pageable pageable) { - JPAQuery countQuery = getCountQuery(categoryId); + JPAQuery countQuery = getCountQueryByCategory(categoryId); return PageableExecutionUtils.getPage(infoPostRes, pageable, countQuery::fetchOne); } - private JPAQuery getCountQuery(Integer categoryId) { + private JPAQuery getCountQueryByCategory(Integer categoryId) { return jpaQueryFactory.select(infoPost.id.count()) .from(infoPost) - .where(createCondition(categoryId)); + .where(buildCategoryCondition(categoryId)); } - private BooleanExpression createCondition(Integer categoryId) { + private BooleanExpression buildCategoryCondition(Integer categoryId) { BooleanExpression condition = infoPost.category.id.eq(categoryId); if (categoryId == 1) { @@ -138,7 +139,7 @@ private BooleanExpression createCondition(Integer categoryId) { return condition; } - public InfoPostUpdateRes.InfoPostInfo findInfoPostForUpdate(Long infoPostId) { + public InfoPostUpdateRes.InfoPostInfo findInfoPostDetailsForUpdate(Long infoPostId) { return jpaQueryFactory.select(Projections.constructor(InfoPostUpdateRes.InfoPostInfo.class, infoPost.title.as("title"), infoPost.category.id.as("categoryId"), @@ -155,4 +156,43 @@ public InfoPostUpdateRes.InfoPostInfo findInfoPostForUpdate(Long infoPostId) { .where(infoPost.id.eq(infoPostId)) .fetchOne(); } + + public Page findInfoPostsByKeyword(String keyword, Pageable pageable) { + List postInfos = jpaQueryFactory.select( + Projections.constructor(InfoPostRes.InfoPostInfo.class, + infoPost.id, + infoPost.image.imageUrl, + infoPost.title, + infoPost.recruitmentEndDate)) + .from(infoPost) + .where(buildKeywordMatchCondition(keyword)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(buildOrderSpecifiers(pageable.getSort())) + .fetch(); + + return paginateInfoPostsByKeyword(keyword, InfoPostRes.of(postInfos), pageable); + } + + private Page paginateInfoPostsByKeyword(String keyword, List infoPostRes, + Pageable pageable) { + JPAQuery countQuery = getCountQueryByKeyword(keyword); + + return PageableExecutionUtils.getPage(infoPostRes, pageable, countQuery::fetchOne); + } + + private JPAQuery getCountQueryByKeyword(String keyword) { + return jpaQueryFactory.select(infoPost.id.count()) + .from(infoPost) + .where(buildKeywordMatchCondition(keyword)); + } + + private BooleanTemplate buildKeywordMatchCondition(String keyword) { + return Expressions.booleanTemplate( + "function('match_against', {0}, {1}, {2})", + infoPost.title, + infoPost.content, + keyword + '*' + ); + } } diff --git a/src/main/java/com/dissonance/itit/service/InfoPostService.java b/src/main/java/com/dissonance/itit/service/InfoPostService.java index ec54880..a8980bb 100644 --- a/src/main/java/com/dissonance/itit/service/InfoPostService.java +++ b/src/main/java/com/dissonance/itit/service/InfoPostService.java @@ -86,7 +86,8 @@ public void deleteInfoPostById(Long infoPostId) { } public InfoPostUpdateRes getInfoPostDetailByIdForUpdate(Long infoPostId) { - InfoPostUpdateRes.InfoPostInfo infoPostInfo = infoPostRepositorySupport.findInfoPostForUpdate(infoPostId); + InfoPostUpdateRes.InfoPostInfo infoPostInfo = infoPostRepositorySupport.findInfoPostDetailsForUpdate( + infoPostId); if (infoPostInfo == null) { throw new CustomException(ErrorCode.NON_EXISTENT_INFO_POST_ID); @@ -130,4 +131,9 @@ private void validateAuthor(InfoPost infoPost, User loginUser) { throw new CustomException(ErrorCode.NO_INFO_POST_UPDATE_PERMISSION); } } + + @Transactional(readOnly = true) + public Page getInfoPostsByKeyword(String keyword, Pageable pageable) { + return infoPostRepositorySupport.findInfoPostsByKeyword(keyword, pageable); + } } diff --git a/src/test/java/com/dissonance/itit/service/InfoPostServiceTest.java b/src/test/java/com/dissonance/itit/service/InfoPostServiceTest.java index a016a63..e581845 100644 --- a/src/test/java/com/dissonance/itit/service/InfoPostServiceTest.java +++ b/src/test/java/com/dissonance/itit/service/InfoPostServiceTest.java @@ -219,7 +219,7 @@ void getInfoPostDetailByIdForUpdate_returnInfoPostUpdateRes() { List positionInfos = TestFixture.createMultiplePositionInfos(); - given(infoPostRepositorySupport.findInfoPostForUpdate(infoPostId)).willReturn(infoPostInfo); + given(infoPostRepositorySupport.findInfoPostDetailsForUpdate(infoPostId)).willReturn(infoPostInfo); given(recruitmentPositionService.findPositionInfosByInfoPostId(infoPostId)).willReturn(positionInfos); // when @@ -236,7 +236,7 @@ void getInfoPostDetailByIdForUpdate_returnInfoPostUpdateRes() { void getInfoPostDetailByIdForUpdate_throwCustomException_givenNonExistentId() { // given Long infoPostId = 999L; - given(infoPostRepositorySupport.findInfoPostForUpdate(infoPostId)).willReturn(null); + given(infoPostRepositorySupport.findInfoPostDetailsForUpdate(infoPostId)).willReturn(null); // when & then assertThatThrownBy(() -> infoPostService.getInfoPostDetailByIdForUpdate(infoPostId)) From 33ed4b9bd96faad36dba606ba05e040fa9954b0a Mon Sep 17 00:00:00 2001 From: jiseon Date: Mon, 11 Nov 2024 17:23:38 +0900 Subject: [PATCH 3/9] =?UTF-8?q?ITDS-60=20fix:=20=EB=82=A0=EC=A7=9C?= =?UTF-8?q?=EA=B0=80=20=EB=91=98=20=EB=8B=A4=20null=EC=9D=B8=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20null=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/dissonance/itit/common/util/DateUtil.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/dissonance/itit/common/util/DateUtil.java b/src/main/java/com/dissonance/itit/common/util/DateUtil.java index c9c467f..c913b02 100644 --- a/src/main/java/com/dissonance/itit/common/util/DateUtil.java +++ b/src/main/java/com/dissonance/itit/common/util/DateUtil.java @@ -22,6 +22,8 @@ public static LocalDate stringToDate(String dateString) { } public static String formatPeriod(LocalDate startDate, LocalDate endDate) { + if (startDate == null && endDate == null) + return null; return formatDate(startDate) + " ~ " + formatDate(endDate); } From 2374920db59acbdcdebf0b4b4e9a4d55a395bc83 Mon Sep 17 00:00:00 2001 From: jiseon Date: Tue, 12 Nov 2024 21:30:02 +0900 Subject: [PATCH 4/9] =?UTF-8?q?refactor:=20=ED=99=98=EA=B2=BD=EB=B3=84=20?= =?UTF-8?q?=EC=B0=A8=EC=9D=B4=EA=B0=80=20=EC=9E=88=EB=8A=94=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=EC=97=90=20${=EB=B3=80=EC=88=98=EB=AA=85:=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=EA=B0=92}=20=ED=98=95=EC=8B=9D=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index af18691..1c03b94 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,4 +1,3 @@ -# TODO: active profile 분리 cors: allowed-origins: "*" @@ -13,17 +12,17 @@ spring: password: ${DB_PASSWORD} hikari: connection-timeout: 2000 - maximum-pool-size: 5 + maximum-pool-size: ${POOL_SIZE:5} driver-class-name: org.mariadb.jdbc.Driver jpa: hibernate: - ddl-auto: update + ddl-auto: ${DDL_AUTO:update} show-sql: true properties: hibernate: format_sql: true dialect: com.dissonance.itit.config.MariaDBFullTextDialect - defer-datasource-initialization: true + defer-datasource-initialization: ${DEFER_INIT:true} profiles: include: oauth servlet: @@ -43,9 +42,8 @@ logging: level: org: hibernate: - type: trace + type: ${LOG_LEVEL:trace} stat: debug - org.springframework.web.reactive.function.client.ExchangeFunctions: TRACE cloud: aws: From 77878e8fb6697c51f212b03def5f338026a68505 Mon Sep 17 00:00:00 2001 From: jiseon Date: Sun, 17 Nov 2024 23:17:11 +0900 Subject: [PATCH 5/9] =?UTF-8?q?ITDS-63=20feat:=20=EA=B0=9C=EB=B0=9C=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EC=97=90=EC=84=9C=EB=A7=8C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=95=8C=EB=A6=BC=EC=9D=84=20=EB=B3=B4=EB=82=B4?= =?UTF-8?q?=EA=B8=B0=20=EC=9C=84=ED=95=B4=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-dev.yml | 52 ++++++++++++++++++++++++ src/main/resources/application-local.yml | 52 ++++++++++++++++++++++++ src/main/resources/application.yml | 50 +---------------------- 3 files changed, 105 insertions(+), 49 deletions(-) create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application-local.yml diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..5af8e2e --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,52 @@ +spring: + datasource: + url: ${DB_URL} + username: ${DB_USER} + password: ${DB_PASSWORD} + hikari: + connection-timeout: 2000 + maximum-pool-size: 10 + driver-class-name: org.mariadb.jdbc.Driver + jpa: + hibernate: + ddl-auto: validate + show-sql: true + properties: + hibernate: + format_sql: true + dialect: com.dissonance.itit.config.MariaDBFullTextDialect + defer-datasource-initialization: false + servlet: + multipart: + max-request-size: 10MB + max-file-size: 10MB + data: + redis: + port: ${REDIS_PORT} + host: ${REDIS_HOST} + +jwt: + token: + secret-key: ${JWT_SECRET} + +logging: + level: + org: + hibernate: + type: debug + stat: debug + discord: + webhook-url: ${DISCORD_WEBHOOK_URL} + config: classpath:logback.xml + +cloud: + aws: + s3: + bucket: itit-bucket + region: + static: ap-northeast-2 + stack: + auto: false + credentials: + access-key: ${AWS_ACCESS_KEY} + secret-key: ${AWS_SECRET_KEY} \ No newline at end of file diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 0000000..97aabc9 --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,52 @@ +spring: + datasource: + url: ${DB_URL} + username: ${DB_USER} + password: ${DB_PASSWORD} + hikari: + connection-timeout: 2000 + maximum-pool-size: 5 + driver-class-name: org.mariadb.jdbc.Driver + jpa: + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + format_sql: true + dialect: com.dissonance.itit.config.MariaDBFullTextDialect + defer-datasource-initialization: true + servlet: + multipart: + max-request-size: 10MB + max-file-size: 10MB + data: + redis: + port: ${REDIS_PORT} + host: ${REDIS_HOST} + +jwt: + token: + secret-key: ${JWT_SECRET} + +logging: + level: + org: + hibernate: + type: trace + stat: debug + discord: + webhook-url: ${DISCORD_WEBHOOK_URL} + config: classpath:logback.xml + +cloud: + aws: + s3: + bucket: itit-bucket + region: + static: ap-northeast-2 + stack: + auto: false + credentials: + access-key: ${AWS_ACCESS_KEY} + secret-key: ${AWS_SECRET_KEY} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 1c03b94..11fff70 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -6,53 +6,5 @@ server: context-path: /api/v1 spring: - datasource: - url: ${DB_URL} - username: ${DB_USER} - password: ${DB_PASSWORD} - hikari: - connection-timeout: 2000 - maximum-pool-size: ${POOL_SIZE:5} - driver-class-name: org.mariadb.jdbc.Driver - jpa: - hibernate: - ddl-auto: ${DDL_AUTO:update} - show-sql: true - properties: - hibernate: - format_sql: true - dialect: com.dissonance.itit.config.MariaDBFullTextDialect - defer-datasource-initialization: ${DEFER_INIT:true} profiles: - include: oauth - servlet: - multipart: - max-request-size: 10MB - max-file-size: 10MB - data: - redis: - port: ${REDIS_PORT} - host: ${REDIS_HOST} - -jwt: - token: - secret-key: ${JWT_SECRET} - -logging: - level: - org: - hibernate: - type: ${LOG_LEVEL:trace} - stat: debug - -cloud: - aws: - s3: - bucket: itit-bucket - region: - static: ap-northeast-2 - stack: - auto: false - credentials: - access-key: ${AWS_ACCESS_KEY} - secret-key: ${AWS_SECRET_KEY} \ No newline at end of file + include: oauth \ No newline at end of file From 162478ce8b38cbed90f50bb1f3f943c78870510f Mon Sep 17 00:00:00 2001 From: jiseon Date: Sun, 17 Nov 2024 23:17:57 +0900 Subject: [PATCH 6/9] =?UTF-8?q?ITDS-63=20feat:=20logback=EC=9D=84=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=9C=20discord=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 + .../exception/GlobalExceptionHandler.java | 6 +-- src/main/resources/Logback.xml | 45 +++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/Logback.xml diff --git a/build.gradle b/build.gradle index 11ccf5e..418e6e2 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,7 @@ configurations { repositories { mavenCentral() + maven { url 'https://jitpack.io' } } dependencies { @@ -64,6 +65,7 @@ dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation('com.github.napstr:logback-discord-appender:1.0.0') } def QDomains = [] diff --git a/src/main/java/com/dissonance/itit/common/exception/GlobalExceptionHandler.java b/src/main/java/com/dissonance/itit/common/exception/GlobalExceptionHandler.java index 8ec0d84..a08eabf 100644 --- a/src/main/java/com/dissonance/itit/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/dissonance/itit/common/exception/GlobalExceptionHandler.java @@ -16,7 +16,7 @@ public class GlobalExceptionHandler { @ExceptionHandler(CustomException.class) protected ResponseEntity> handleCustomException(CustomException e) { int statusCode = e.getErrorCode().getHttpStatus().value(); - log.error("CustomException : {}", e.getMessage()); + log.error("CustomException: ", e); return new ResponseEntity<>(ApiResponse.error(statusCode, e.getMessage()), e.getErrorCode().getHttpStatus()); } @@ -24,7 +24,7 @@ protected ResponseEntity> handleCustomException(CustomException e @ExceptionHandler(Exception.class) public ResponseEntity> handleAllException(final Exception e) { int statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value(); - log.error("handleAllException {}", e.getMessage()); + log.error("handleAllException: ", e); return new ResponseEntity<>(ApiResponse.error(statusCode, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); } @@ -32,7 +32,7 @@ public ResponseEntity> handleAllException(final Exception e) { @ExceptionHandler(MaxUploadSizeExceededException.class) public ResponseEntity> handleMaxSizeException(MaxUploadSizeExceededException e) { int statusCode = e.getStatusCode().value(); - log.error("MaxUploadSizeExceededException {}", e.getMessage()); + log.error("MaxUploadSizeExceededException: ", e); return new ResponseEntity<>(ApiResponse.error(statusCode, e.getMessage()), HttpStatus.PAYLOAD_TOO_LARGE); } diff --git a/src/main/resources/Logback.xml b/src/main/resources/Logback.xml new file mode 100644 index 0000000..1ff0373 --- /dev/null +++ b/src/main/resources/Logback.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + ${DISCORD_WEBHOOK_URL} + + %d{HH:mm:ss} [%thread] [%-5level] %logger{36} - %msg%n```%ex{full}``` + + 🚨에러 알림🚨 + + https://img1.daumcdn.net/thumb/R1280x0.fjpg/?fname=http://t1.daumcdn.net/brunch/service/user/cnoC/image/vuyIRQbt6-tQGfW80jNaVS5zjTw + + false + + + + + ${CONSOLE_LOG_PATTERN} + utf8 + + + + + + + ERROR + + + + + + + + + \ No newline at end of file From fcf109c03bd3d22512e07c7a26931ba4d09a22d5 Mon Sep 17 00:00:00 2001 From: jiseon Date: Tue, 19 Nov 2024 15:49:04 +0900 Subject: [PATCH 7/9] =?UTF-8?q?ITDS-63=20refactor:=20exception=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/dissonance/itit/common/exception/GlobalExceptionHandler.java b/src/main/java/com/dissonance/itit/common/exception/GlobalExceptionHandler.java index a08eabf..a2c29c3 100644 --- a/src/main/java/com/dissonance/itit/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/dissonance/itit/common/exception/GlobalExceptionHandler.java @@ -16,24 +16,27 @@ public class GlobalExceptionHandler { @ExceptionHandler(CustomException.class) protected ResponseEntity> handleCustomException(CustomException e) { int statusCode = e.getErrorCode().getHttpStatus().value(); - log.error("CustomException: ", e); - return new ResponseEntity<>(ApiResponse.error(statusCode, e.getMessage()), - e.getErrorCode().getHttpStatus()); + log.error("CustomException: {}", e.getMessage(), e); + return ResponseEntity + .status(e.getErrorCode().getHttpStatus()) + .body(ApiResponse.error(statusCode, e.getMessage())); } @ExceptionHandler(Exception.class) public ResponseEntity> handleAllException(final Exception e) { int statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value(); - log.error("handleAllException: ", e); - return new ResponseEntity<>(ApiResponse.error(statusCode, e.getMessage()), - HttpStatus.INTERNAL_SERVER_ERROR); + log.error("handleAllException: {}", e.getMessage(), e); + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error(statusCode, e.getMessage())); } @ExceptionHandler(MaxUploadSizeExceededException.class) public ResponseEntity> handleMaxSizeException(MaxUploadSizeExceededException e) { int statusCode = e.getStatusCode().value(); - log.error("MaxUploadSizeExceededException: ", e); - return new ResponseEntity<>(ApiResponse.error(statusCode, e.getMessage()), - HttpStatus.PAYLOAD_TOO_LARGE); + log.error("MaxUploadSizeExceededException: {}", e.getMessage(), e); + return ResponseEntity + .status(HttpStatus.PAYLOAD_TOO_LARGE) + .body(ApiResponse.error(statusCode, e.getMessage())); } } From 9b58fc4bdd4659b754d3412a4690d5d0a4d19b92 Mon Sep 17 00:00:00 2001 From: jiseon Date: Tue, 19 Nov 2024 15:49:48 +0900 Subject: [PATCH 8/9] =?UTF-8?q?ITDS-63=20feat:=20CI/CD=20workflow=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=8B=9C=EC=9E=91=20=EC=A0=84=ED=9B=84=20?= =?UTF-8?q?discord=20=EC=95=8C=EB=A6=BC=20=EC=A0=84=EC=86=A1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cicd.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 930a2e0..5629902 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -66,6 +66,12 @@ jobs: run: | aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 + # 배포 시작 알림 전송 + - name: Start Deployment + continue-on-error: true + run: | + curl -X POST -H "Content-Type: application/json" --data '{"content":"Deployment started!"}' ${{ secrets.DISCORD_WEBHOOK }} + # ssh로 접속해 재배포 - name: Deploy uses: appleboy/ssh-action@master @@ -79,7 +85,18 @@ jobs: docker-compose pull docker-compose up -d + # 배포 완료 알림 전송 + - name: Notify Deployment Completed + if: success() + run: | + curl -X POST -H "Content-Type: application/json" --data '{"content":"Deployment completed successfully!"}' ${{ secrets.DISCORD_WEBHOOK }} + - name: Notify Deployment Failed + if: failure() + run: | + curl -X POST -H "Content-Type: application/json" --data '{"content":"Deployment failed!"}' ${{ secrets.DISCORD_WEBHOOK }} + # 배포 후 보안 그룹에서 github ip 삭제 - name: Remove Github Actions IP From Security Group + if: always() run: | aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 \ No newline at end of file From 4c4afd2f56a1640614c161ee15ba6a65fb5b209b Mon Sep 17 00:00:00 2001 From: jiseon Date: Tue, 19 Nov 2024 15:52:09 +0900 Subject: [PATCH 9/9] =?UTF-8?q?ITDS-63=20fix:=20=EB=AA=A8=EC=A7=91=20?= =?UTF-8?q?=EB=A7=88=EA=B0=90=EC=9D=BC=20not=20null=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/dissonance/itit/domain/entity/InfoPost.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/dissonance/itit/domain/entity/InfoPost.java b/src/main/java/com/dissonance/itit/domain/entity/InfoPost.java index 5533bc1..b3809c1 100644 --- a/src/main/java/com/dissonance/itit/domain/entity/InfoPost.java +++ b/src/main/java/com/dissonance/itit/domain/entity/InfoPost.java @@ -52,6 +52,7 @@ public class InfoPost extends BaseTime { @Column(name = "recruitment_start_date") private LocalDate recruitmentStartDate; + @NotNull @Column(name = "recruitment_end_date") private LocalDate recruitmentEndDate;