Skip to content

Commit

Permalink
Merge pull request #41 from DDD-Community/develop
Browse files Browse the repository at this point in the history
8차 배포
  • Loading branch information
kikingki authored Nov 19, 2024
2 parents d02379f + abf1ecd commit 1b0aa7a
Show file tree
Hide file tree
Showing 14 changed files with 278 additions and 74 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ configurations {

repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}

dependencies {
Expand Down Expand Up @@ -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 = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,27 @@ public class GlobalExceptionHandler {
@ExceptionHandler(CustomException.class)
protected ResponseEntity<ApiResponse<?>> handleCustomException(CustomException e) {
int statusCode = e.getErrorCode().getHttpStatus().value();
log.error("CustomException : {}", e.getMessage());
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<ApiResponse<?>> handleAllException(final Exception e) {
int statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
log.error("handleAllException {}", e.getMessage());
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<ApiResponse<?>> handleMaxSizeException(MaxUploadSizeExceededException e) {
int statusCode = e.getStatusCode().value();
log.error("MaxUploadSizeExceededException {}", e.getMessage());
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()));
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/dissonance/itit/common/util/DateUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -42,10 +43,18 @@ public ApiResponse<String> reportedInfoPost(@PathVariable Long infoPostId, @Curr

@GetMapping("/categories/{categoryId}/posts")
@Operation(summary = "공고 게시글 목록 조회", description = "카테고리별 공고 게시글 목록을 조회합니다. (정렬: 최신순 - latest, 마감일순 - deadline)")
public ApiResponse<Page<InfoPostRes>> getInfoPostsByCategory(@PathVariable Integer categoryId,
Pageable pageable) {
public ApiResponse<Page<InfoPostRes>> getInfoPostsByCategory(@PathVariable Integer categoryId, Pageable pageable) {
Page<InfoPostRes> infoPostRes = infoPostService.getInfoPostsByCategoryId(categoryId, pageable);

return ApiResponse.success(infoPostRes);
}

@GetMapping("/search")
@Operation(summary = "공고 게시글 키워드 검색", description = "공고 게시글을 키워드로 검색합니다.")
public ApiResponse<Page<InfoPostRes>> getInfoPostsByKeyword(@RequestParam String keyword,
Pageable pageable) {
Page<InfoPostRes> infoPostRes = infoPostService.getInfoPostsByKeyword(keyword, pageable);

return ApiResponse.success(infoPostRes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -67,16 +68,16 @@ public Page<InfoPostRes> 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<OrderSpecifier<?>> orderSpecifiers = new ArrayList<>();

for (Sort.Order order : sort) {
Expand Down Expand Up @@ -115,20 +116,20 @@ public OrderSpecifier<?>[] getOrderSpecifiers(Sort sort) {
}
}

private Page<InfoPostRes> paginationInfoPosts(Integer categoryId, List<InfoPostRes> infoPostRes,
private Page<InfoPostRes> paginateInfoPostsByCategory(Integer categoryId, List<InfoPostRes> infoPostRes,
Pageable pageable) {
JPAQuery<Long> countQuery = getCountQuery(categoryId);
JPAQuery<Long> countQuery = getCountQueryByCategory(categoryId);

return PageableExecutionUtils.getPage(infoPostRes, pageable, countQuery::fetchOne);
}

private JPAQuery<Long> getCountQuery(Integer categoryId) {
private JPAQuery<Long> 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) {
Expand All @@ -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"),
Expand All @@ -155,4 +156,43 @@ public InfoPostUpdateRes.InfoPostInfo findInfoPostForUpdate(Long infoPostId) {
.where(infoPost.id.eq(infoPostId))
.fetchOne();
}

public Page<InfoPostRes> findInfoPostsByKeyword(String keyword, Pageable pageable) {
List<InfoPostRes.InfoPostInfo> 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<InfoPostRes> paginateInfoPostsByKeyword(String keyword, List<InfoPostRes> infoPostRes,
Pageable pageable) {
JPAQuery<Long> countQuery = getCountQueryByKeyword(keyword);

return PageableExecutionUtils.getPage(infoPostRes, pageable, countQuery::fetchOne);
}

private JPAQuery<Long> 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 + '*'
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<InfoPostRes> getInfoPostsByKeyword(String keyword, Pageable pageable) {
return infoPostRepositorySupport.findInfoPostsByKeyword(keyword, pageable);
}
}
45 changes: 45 additions & 0 deletions src/main/resources/Logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>

<springProfile name="dev">
<property resource="application-dev.yml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<springProfile name="local">
<property resource="application-local.yml"/>
<springProperty name="DISCORD_WEBHOOK_URL" source="logging.discord.webhook-url"/>
<appender name="DISCORD" class="com.github.napstr.logback.DiscordAppender">
<webhookUri>${DISCORD_WEBHOOK_URL}</webhookUri>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{HH:mm:ss} [%thread] [%-5level] %logger{36} - %msg%n```%ex{full}```</pattern>
</layout>
<username>🚨에러 알림🚨</username>
<avatarUrl>
https://img1.daumcdn.net/thumb/R1280x0.fjpg/?fname=http://t1.daumcdn.net/brunch/service/user/cnoC/image/vuyIRQbt6-tQGfW80jNaVS5zjTw
</avatarUrl>
<tts>false</tts>
</appender>

<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<charset>utf8</charset>
</encoder>
</appender>

<appender name="ASYNC_DISCORD" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="DISCORD"/>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>

<root level="INFO">
<appender-ref ref="ASYNC_DISCORD"/>
<appender-ref ref="Console"/>
</root>
</springProfile>
</configuration>
52 changes: 52 additions & 0 deletions src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
@@ -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}
Loading

0 comments on commit 1b0aa7a

Please sign in to comment.