Skip to content

Commit

Permalink
[Release] 리뷰미 v2.0.0 배포
Browse files Browse the repository at this point in the history
- 섹션별, 질문별 모아보기 기능이 추가되었습니다.
- 모아보기 페이지에서 형광펜을 사용할 수 있습니다.
  • Loading branch information
nayonsoso authored Oct 23, 2024
2 parents c1dd036 + a92b852 commit 7b572c7
Show file tree
Hide file tree
Showing 286 changed files with 8,483 additions and 948 deletions.
82 changes: 82 additions & 0 deletions .github/workflows/zero-downtime-deploy-test-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: "[test] Zero Downtime Deploy Test CD"

on:
workflow_dispatch:

env:
APPLICATION_DIRECTORY: /home/ubuntu/review-me

jobs:
build:
name: Build Dockerfile and push to DockerHub
runs-on: ubuntu-latest

steps:
- name: Checkout to current repository
uses: actions/checkout@v4

- name: Setup JDK Corretto using cached gradle dependencies
uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: 17
cache: 'gradle'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
gradle-version: 8.8

- name: Build and test with gradle
run: |
cd ./backend
./gradlew clean bootJar
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ID }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push
uses: docker/build-push-action@v6
with:
context: ./backend
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ secrets.DOCKERHUB_ID }}/review-me-app:develop

deploy:
name: Deploy via self-hosted runner
needs: build
runs-on: [self-hosted, dev]

steps:
- name: Checkout to secret repository
uses: actions/checkout@v4
with:
repository: ${{ secrets.PRIVATE_REPOSITORY_URL }}
token: ${{ secrets.PRIVATE_REPOSITORY_TOKEN }}

- name: Move application-related files to local
run: |
mkdir -p ${{ env.APPLICATION_DIRECTORY }}/app
mv ./app/* ./app/.* ${{ env.APPLICATION_DIRECTORY }}/app
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ID }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Deploy new version # 변경 부분
env:
PROFILE_VAR: "dev"
run: |
chmod +x ./deploy.sh
sudo -E ./deploy.sh
working-directory: ${{ env.APPLICATION_DIRECTORY }}/app
1 change: 1 addition & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencies {
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
Expand Down
3 changes: 3 additions & 0 deletions backend/src/docs/asciidoc/highlight-answers.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
==== 리뷰 하이라이트 변경 (추가, 삭제, 수정 포함)

operation::highlight-answer[snippets="curl-request,request-cookies,http-response,request-fields"]
11 changes: 11 additions & 0 deletions backend/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,21 @@ include::create-review.adoc[]

== 리뷰 조회

=== 리뷰 요약 조회
include::review-summary.adoc[]

=== 리뷰 단건 조회

include::review-detail.adoc[]

=== 리뷰 목록 조회

include::review-list.adoc[]

=== 리뷰 모아보기

include::review-gather.adoc[]

=== 답변 하이라이트

include::highlight-answers.adoc[]
7 changes: 7 additions & 0 deletions backend/src/docs/asciidoc/review-gather.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
==== 섹션 이름 목록 가져오기

operation::get-session-names[snippets="curl-request,request-cookies,http-response,response-fields"]

==== 받은 리뷰 섹션별 모아보기

operation::received-review-by-section[snippets="curl-request,request-cookies,query-parameters,http-response,response-fields"]
3 changes: 3 additions & 0 deletions backend/src/docs/asciidoc/review-summary.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
==== 자신이 받은 리뷰 요약 조회

operation::received-review-summary[snippets="curl-request,request-cookies,http-response,response-fields"]
13 changes: 13 additions & 0 deletions backend/src/main/java/reviewme/config/RequestLimitProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package reviewme.config;

import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "request-limit")
public record RequestLimitProperties(
long threshold,
Duration duration,
String host,
int port
) {
}
34 changes: 34 additions & 0 deletions backend/src/main/java/reviewme/config/RequestLimitRedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package reviewme.config;

import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.serializer.GenericToStringSerializer;

@Configuration
@EnableConfigurationProperties(RequestLimitProperties.class)
@RequiredArgsConstructor
public class RequestLimitRedisConfig {

private final RequestLimitProperties requestLimitProperties;

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(
requestLimitProperties.host(), requestLimitProperties.port()
);
}

@Bean
public RedisTemplate<String, Long> requestLimitRedisTemplate() {
RedisTemplate<String, Long> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setValueSerializer(new GenericToStringSerializer<>(Long.class));

return redisTemplate;
}
}
19 changes: 17 additions & 2 deletions backend/src/main/java/reviewme/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
package reviewme.config;

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import reviewme.global.HeaderPropertyArgumentResolver;
import reviewme.global.RequestLimitInterceptor;
import reviewme.reviewgroup.controller.ReviewGroupSessionResolver;
import reviewme.reviewgroup.service.ReviewGroupService;

@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

private final ReviewGroupService reviewGroupService;
private final RedisTemplate<String, Long> redisTemplate;
private final RequestLimitProperties requestLimitProperties;

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new HeaderPropertyArgumentResolver());
resolvers.add(new ReviewGroupSessionResolver(reviewGroupService));
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RequestLimitInterceptor(redisTemplate, requestLimitProperties));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.springframework.web.servlet.resource.NoResourceFoundException;
import reviewme.global.exception.BadRequestException;
import reviewme.global.exception.DataInconsistencyException;
import reviewme.global.exception.TooManyRequestException;
import reviewme.global.exception.FieldErrorResponse;
import reviewme.global.exception.NotFoundException;
import reviewme.global.exception.UnauthorizedException;
Expand Down Expand Up @@ -50,6 +51,11 @@ public ProblemDetail handleDataConsistencyException(DataInconsistencyException e
return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.getErrorMessage());
}

@ExceptionHandler(TooManyRequestException.class)
public ProblemDetail handleDuplicateRequestException(TooManyRequestException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.TOO_MANY_REQUESTS, ex.getErrorMessage());
}

@ExceptionHandler(Exception.class)
public ProblemDetail handleException(Exception ex) {
log.error("Internal server error has occurred", ex);
Expand Down
18 changes: 0 additions & 18 deletions backend/src/main/java/reviewme/global/HeaderProperty.java

This file was deleted.

This file was deleted.

50 changes: 50 additions & 0 deletions backend/src/main/java/reviewme/global/RequestLimitInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package reviewme.global;

import static org.springframework.http.HttpHeaders.USER_AGENT;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import reviewme.config.RequestLimitProperties;
import reviewme.global.exception.TooManyRequestException;

@Component
@EnableConfigurationProperties(RequestLimitProperties.class)
@RequiredArgsConstructor
public class RequestLimitInterceptor implements HandlerInterceptor {

private final RedisTemplate<String, Long> redisTemplate;
private final RequestLimitProperties requestLimitProperties;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (!HttpMethod.POST.matches(request.getMethod())) {
return true;
}

String key = generateRequestKey(request);
ValueOperations<String, Long> valueOperations = redisTemplate.opsForValue();
valueOperations.setIfAbsent(key, 0L, requestLimitProperties.duration());
redisTemplate.expire(key, requestLimitProperties.duration());

long requestCount = valueOperations.increment(key);
if (requestCount > requestLimitProperties.threshold()) {
throw new TooManyRequestException(key);
}
return true;
}

private String generateRequestKey(HttpServletRequest request) {
String requestURI = request.getRequestURI();
String remoteAddr = request.getRemoteAddr();
String userAgent = request.getHeader(USER_AGENT);

return String.format("RequestURI: %s, RemoteAddr: %s, UserAgent: %s", requestURI, remoteAddr, userAgent);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package reviewme.global.exception;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TooManyRequestException extends ReviewMeException {

public TooManyRequestException(String requestKey) {
super("짧은 시간 안에 너무 많은 동일한 요청이 일어났어요. 잠시 후 다시 시도해주세요.");
log.warn("Too many request received - request: {}", requestKey);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package reviewme.highlight.controller;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import reviewme.highlight.service.HighlightService;
import reviewme.highlight.service.dto.HighlightsRequest;
import reviewme.reviewgroup.controller.ReviewGroupSession;
import reviewme.reviewgroup.domain.ReviewGroup;

@RestController
@RequiredArgsConstructor
public class HighlightController {

private final HighlightService highlightService;

@PostMapping("/v2/highlight")
public ResponseEntity<Void> highlight(
@Valid @RequestBody HighlightsRequest request,
@ReviewGroupSession ReviewGroup reviewGroup
) {
highlightService.editHighlight(request, reviewGroup);
return ResponseEntity.ok().build();
}
}
Loading

0 comments on commit 7b572c7

Please sign in to comment.