From a6794a26a44ce3e1b01baed0c9fa7e3b16a5ef9a Mon Sep 17 00:00:00 2001 From: marshmallowing Date: Sun, 26 Oct 2025 22:23:04 +0900 Subject: [PATCH 01/13] =?UTF-8?q?deploy:=20=EC=84=9C=EB=B2=84=20https=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 22 ++++++++++++++++++++++ nginx/nginx.conf | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 nginx/nginx.conf diff --git a/docker-compose.yml b/docker-compose.yml index 50ec9e7..81b93b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,5 @@ +version: '3.8' + services: growin-api: image: ghcr.io/growin-2025/growin-server:latest @@ -6,3 +8,23 @@ services: - "8080:8080" env_file: - .env + + nginx: + container_name: nginx + image: nginx:latest + ports: + - "80:80" + - "443:443" + volumes: + - ./data/nginx:/etc/nginx/conf.d + - ./data/certbot/conf:/etc/letsencrypt + - ./data/certbot/www:/var/www/certbot + restart: always + + certbot: + container_name: certbot + image: certbot/certbot + volumes: + - ./data/certbot/conf:/etc/letsencrypt + - ./data/certbot/www:/var/www/certbot + entrypoint: /bin/sh -c "trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;" diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..1f2ead1 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,32 @@ +# HTTP → HTTPS 리다이렉트 +server { + listen 80; + server_name growinserver.shop; + server_tokens off; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + allow all; + } + + location / { + return 301 https://$host$request_uri; + } +} + +# HTTPS 설정 +server { + listen 443 ssl; + server_name growinserver.shop; + + ssl_certificate /etc/letsencrypt/live/growinserver.shop/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/growinserver.shop/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + location / { + proxy_pass http://growin-api:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} From f46f40ba19bd7b1d6aaf64892428c25bd289b6c9 Mon Sep 17 00:00:00 2001 From: marshmallowing Date: Sun, 26 Oct 2025 22:49:15 +0900 Subject: [PATCH 02/13] =?UTF-8?q?fix:=20cd=20=EA=B3=BC=EC=A0=95=EC=97=90?= =?UTF-8?q?=EC=84=9C=20Nginx=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd.yml | 8 ++++---- nginx/{nginx.conf => default.conf} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename nginx/{nginx.conf => default.conf} (100%) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index bb50f5e..99d72c1 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -25,7 +25,7 @@ jobs: username: ${{ secrets.SSH_USERNAME }} key: ${{ secrets.SSH_KEY }} port: 22 - source: "docker-compose.yml,.env" + source: "docker-compose.yml,.env,nginx/" target: "/home/ubuntu/growin/" # EC2에서 도커 컨테이너 재배포 @@ -43,10 +43,10 @@ jobs: export GHCR_USERNAME=${{ github.actor }} echo $GHCR_TOKEN | docker login ghcr.io -u $GHCR_USERNAME --password-stdin - sudo docker-compose pull + sudo docker compose pull - sudo docker-compose down || true + sudo docker compose down || true - sudo docker-compose up -d + sudo docker compose up -d sudo docker image prune -a -f diff --git a/nginx/nginx.conf b/nginx/default.conf similarity index 100% rename from nginx/nginx.conf rename to nginx/default.conf From b3e1a50fb422eaf8be36e66085cfac6379617f87 Mon Sep 17 00:00:00 2001 From: marshmallowing Date: Sun, 26 Oct 2025 23:00:48 +0900 Subject: [PATCH 03/13] =?UTF-8?q?fix:=20nginx=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 81b93b0..0c8f30e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,7 @@ services: - "80:80" - "443:443" volumes: - - ./data/nginx:/etc/nginx/conf.d + - ./nginx:/etc/nginx/conf.d - ./data/certbot/conf:/etc/letsencrypt - ./data/certbot/www:/var/www/certbot restart: always From 7062aad356173fa583999d4abbdeca20420c359d Mon Sep 17 00:00:00 2001 From: Lim Wonjae Date: Sat, 1 Nov 2025 11:59:03 +0900 Subject: [PATCH 04/13] =?UTF-8?q?pr=20=EC=98=A4=ED=94=88=20=EC=8B=9C=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr_upload_notify.yml | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/pr_upload_notify.yml diff --git a/.github/workflows/pr_upload_notify.yml b/.github/workflows/pr_upload_notify.yml new file mode 100644 index 0000000..2cd1d6b --- /dev/null +++ b/.github/workflows/pr_upload_notify.yml @@ -0,0 +1,49 @@ +name: Notify Slack on Pull Request Upload + +on: + pull_request: + types: [opened] + +jobs: + slack_notification: + runs-on: ubuntu-latest + steps: + - name: Send Slack notification + uses: slackapi/slack-github-action@v1.27.0 + with: + payload: | + { + "text": ":bell: *New Pull Request!*", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": ":sparkles: *${{ github.actor }}* opened a PR" + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Title:*\n${{ github.event.pull_request.title }}" + }, + { + "type": "mrkdwn", + "text": "*Base Branch:*\n${{ github.base_ref }}" + }, + { + "type": "mrkdwn", + "text": "*From Branch:*\n${{ github.head_ref }}" + }, + { + "type": "mrkdwn", + "text": "*Link:*\n<${{ github.event.pull_request.html_url }}|View PR>" + } + ] + } + ] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} From d1e7b86754e0a3eb87f22aaf1cc5efc12498301e Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Wed, 5 Nov 2025 09:34:01 +0900 Subject: [PATCH 05/13] =?UTF-8?q?feat=20:=20data=20=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=20=EC=9D=91=EB=8B=B5=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task/controller/TaskController.java | 82 +++++++++++++++++++ .../growin/global/response/APIResponse.java | 8 +- 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ita/growin/domain/task/controller/TaskController.java diff --git a/src/main/java/ita/growin/domain/task/controller/TaskController.java b/src/main/java/ita/growin/domain/task/controller/TaskController.java new file mode 100644 index 0000000..2014210 --- /dev/null +++ b/src/main/java/ita/growin/domain/task/controller/TaskController.java @@ -0,0 +1,82 @@ +package ita.growin.domain.task.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import ita.growin.domain.task.dto.CreateTaskRequestDto; +import ita.growin.domain.task.dto.UpdateTaskRequestDto; +import ita.growin.domain.task.service.TaskService; +import ita.growin.global.response.APIResponse; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.web.PageableDefault; +import org.springframework.lang.Nullable; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/tasks") +@Tag(name = "할 일", description = "할 일 관련 API") +public class TaskController { + + private final TaskService taskService; + + public TaskController(TaskService taskService) { + this.taskService = taskService; + } + + @PostMapping + @Operation(summary = "할 일 생성 API") + public APIResponse createTask( + @RequestParam("eventId") @Nullable Long eventId, + @RequestBody CreateTaskRequestDto req) { + taskService.createTask(eventId, req); + return APIResponse.success(); + } + + @PatchMapping("/{taskId}") + @Operation(summary = "할 일 수정 API") + public APIResponse updateTask( + @PathVariable Long taskId, + @RequestBody UpdateTaskRequestDto req) { + taskService.updateTask(taskId, req); + return APIResponse.success(); + } + + @DeleteMapping("/{taskId}") + @Operation(summary = "할 일 삭제 API") + public APIResponse deleteTask( + @RequestParam("eventId") @Nullable Long eventId, + @PathVariable Long taskId) { + taskService.deleteTask(eventId, taskId); + return APIResponse.success(); + } + + @GetMapping("/{taskId}") + @Operation(summary = "할 일 단건 조회") + public APIResponse getTask(@PathVariable Long taskId) { + return APIResponse.success(taskService.getTask(taskId)); + } + + @GetMapping("/event/{eventId}/tasks") + @Operation(summary = "일정 내 할 일 조회") + public APIResponse> getTasks( + @PathVariable Long eventId, + @PageableDefault(size = 20) Pageable pageable) { + return APIResponse.success(taskService.getTasks(eventId, pageable)); + } + + @GetMapping("/tasks/today") + @Operation(summary = "오늘 할 일 조회") + public APIResponse getTodayTasks( + @PageableDefault(size = 20) Pageable pageable + ) { + return APIResponse.success(taskService.getTodayTasks(pageable)); + } + + @GetMapping("/tasks/someday") + @Operation(summary = "언젠가 할 일 조회") + public APIResponse getSomedayTasks( + @PageableDefault(size = 20) Pageable pageable + ) { + return APIResponse.success(taskService.getSomedayTasks(pageable)); + } +} diff --git a/src/main/java/ita/growin/global/response/APIResponse.java b/src/main/java/ita/growin/global/response/APIResponse.java index 2635f34..3a78272 100644 --- a/src/main/java/ita/growin/global/response/APIResponse.java +++ b/src/main/java/ita/growin/global/response/APIResponse.java @@ -16,7 +16,13 @@ public record APIResponse( private static final String SUCCESS_MESSAGE = "요청이 성공적으로 처리되었습니다."; - // 성공응답 + // 성공응답 (data X) + public static APIResponse success() { + return new APIResponse<>( + HttpStatus.OK.value(), null, SUCCESS_MESSAGE, LocalDateTime.now(), null); + } + + // 성공응답 (data 존재) public static APIResponse success(T data) { return new APIResponse<>( HttpStatus.OK.value(), null, SUCCESS_MESSAGE, LocalDateTime.now(), data); From c73718390662bfa5e6cfcb6af0ad12f1b992d931 Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Wed, 5 Nov 2025 11:01:35 +0900 Subject: [PATCH 06/13] =?UTF-8?q?feat=20:=20Security=20=EC=9E=84=EC=8B=9C?= =?UTF-8?q?=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../growin/global/config/SecurityConfig.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/ita/growin/global/config/SecurityConfig.java diff --git a/src/main/java/ita/growin/global/config/SecurityConfig.java b/src/main/java/ita/growin/global/config/SecurityConfig.java new file mode 100644 index 0000000..61bb16b --- /dev/null +++ b/src/main/java/ita/growin/global/config/SecurityConfig.java @@ -0,0 +1,20 @@ +package ita.growin.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) + .formLogin(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable); + return http.build(); + } +} From 873f7fcc1f37d8585da49c23623de268a29fdd1b Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Wed, 5 Nov 2025 11:01:51 +0900 Subject: [PATCH 07/13] =?UTF-8?q?feat=20:=20docker=20compose=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=B6=84=EB=A6=AC=20(common,=20blue,=20green)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/docker-compose.blue.yml | 16 ++++++++++++++++ .../docker-compose.common.yml | 13 +++++-------- docker/docker-compose.green.yml | 16 ++++++++++++++++ 3 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 docker/docker-compose.blue.yml rename docker-compose.yml => docker/docker-compose.common.yml (78%) create mode 100644 docker/docker-compose.green.yml diff --git a/docker/docker-compose.blue.yml b/docker/docker-compose.blue.yml new file mode 100644 index 0000000..4203a6d --- /dev/null +++ b/docker/docker-compose.blue.yml @@ -0,0 +1,16 @@ +version: '3.8' + +services: + growin-api-blue: + image: ghcr.io/growin-2025/growin-server:latest + container_name: growin-api-blue + ports: + - "8080:8080" + env_file: + - .env + networks: + - app-network + +networks: + app-network: + external: true \ No newline at end of file diff --git a/docker-compose.yml b/docker/docker-compose.common.yml similarity index 78% rename from docker-compose.yml rename to docker/docker-compose.common.yml index 0c8f30e..8b6219d 100644 --- a/docker-compose.yml +++ b/docker/docker-compose.common.yml @@ -1,14 +1,6 @@ version: '3.8' services: - growin-api: - image: ghcr.io/growin-2025/growin-server:latest - container_name: growin-api - ports: - - "8080:8080" - env_file: - - .env - nginx: container_name: nginx image: nginx:latest @@ -28,3 +20,8 @@ services: - ./data/certbot/conf:/etc/letsencrypt - ./data/certbot/www:/var/www/certbot entrypoint: /bin/sh -c "trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;" + +networks: + app-network: + name: growin-network + driver: bridge \ No newline at end of file diff --git a/docker/docker-compose.green.yml b/docker/docker-compose.green.yml new file mode 100644 index 0000000..6d597bf --- /dev/null +++ b/docker/docker-compose.green.yml @@ -0,0 +1,16 @@ +version: '3.8' + +services: + growin-api-green: + image: ghcr.io/growin-2025/growin-server:latest + container_name: growin-api-green + ports: + - "8081:8080" + env_file: + - .env + networks: + - app-network + +networks: + app-network: + external: true \ No newline at end of file From a69516373cc5f5f53a88e6fc35e45d3cc1ac0836 Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Wed, 5 Nov 2025 11:02:04 +0900 Subject: [PATCH 08/13] =?UTF-8?q?feat=20:=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy.sh | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 deploy.sh diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..383f561 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +APP_NAME=growin-api +BLUE_PORT=8080 +GREEN_PORT=8081 +NGINX_CONF=/home/ubuntu/growin/nginx/default.conf + + +echo "deploy start" + +if ! docker ps --format '{{.Names}}' | grep -q "${APP_NAME}_blue" && \ + ! docker ps --format '{{.Names}}' | grep -q "${APP_NAME}_green"; then + echo "First deployment detected — starting blue container..." + docker-compose -f docker-compose.blue.yml up -d + exit 0 +fi + + +if docker ps | grep -q "${APP_NAME}-blue"; then + CURRENT="blue" + NEXT="green" + CURRENT_PORT=$BLUE_PORT + NEXT_PORT=$GREEN_PORT +else + CURRENT="green" + NEXT="blue" + CURRENT_PORT=$GREEN_PORT + NEXT_PORT=$BLUE_PORT +fi + + +ehco "Current Container : $CURRENT" +echo "Next Container : $NEXT" + +echo "deploy $NEXT Container" +docker-compose -f docker-compose.${NEXT}.yml up -d + + +echo "running health check" +success=false +for i in {1..20}; do + sleep 3 + if curl -fs "http://localhost:${NEXT_PORT}/test/health" | grep -q "UP"; then + ehco "Health Check Passed" + success=true + break + fi + echo "Waiting for Service to be UP ... (${i}/10)" +done + + +# 실행 실패 시 -> 롤백 진행 후 종료 +if [ "$success" = false ]; then + echo "Health check failed! Rolling back..." + docker-compose -f docker-compose.${NEXT}.yml down + exit 1 +fi + + +# Reload Nginx +echo "if success, switch nginx conf and stop old container" + sudo sed -i "s/${APP_NAME}-${CURRENT}/${APP_NAME}-${NEXT}/" $NGINX_CONF + sudo sed -i "s/${CURRENT_PORT}/${NEXT_PORT}/" $NGINX_CONF + sudo docker exec nginx nginx -s reload + +# Stop old container +echo "==> Stopping old container ${APP_NAME}_${CURRENT}" +docker stop ${APP_NAME}_${CURRENT} || true +docker rm ${APP_NAME}_${CURRENT} || true + +echo "Cleaning unused images" +docker image prune -f >/dev/null 2>&1 + +echo "==============================" +echo "DEPLOYMENT SUCCESSFUL" +echo "Active container: ${NEXT}" +echo "==============================" \ No newline at end of file From fa1488486678c5039b97e4efaa5b60366ef97c03 Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Wed, 5 Nov 2025 11:02:15 +0900 Subject: [PATCH 09/13] =?UTF-8?q?feat=20:=20nginx=20=ED=8F=AC=ED=8A=B8=20?= =?UTF-8?q?=ED=8F=AC=EC=9B=8C=EB=94=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nginx/default.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx/default.conf b/nginx/default.conf index 1f2ead1..4414807 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -25,7 +25,7 @@ server { ssl_ciphers HIGH:!aNULL:!MD5; location / { - proxy_pass http://growin-api:8080; + proxy_pass http://growin-api-blue:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } From 658401ec1e7aecc93f376a4118a4b1c9a44e0acd Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Wed, 5 Nov 2025 11:02:22 +0900 Subject: [PATCH 10/13] =?UTF-8?q?feat=20:=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EB=A1=9C=EC=A7=81=20=EC=8B=A4=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 99d72c1..dda0860 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -25,7 +25,7 @@ jobs: username: ${{ secrets.SSH_USERNAME }} key: ${{ secrets.SSH_KEY }} port: 22 - source: "docker-compose.yml,.env,nginx/" + source: "docker/,.env,nginx/,deploy.sh" target: "/home/ubuntu/growin/" # EC2에서 도커 컨테이너 재배포 @@ -43,10 +43,8 @@ jobs: export GHCR_USERNAME=${{ github.actor }} echo $GHCR_TOKEN | docker login ghcr.io -u $GHCR_USERNAME --password-stdin - sudo docker compose pull - - sudo docker compose down || true - - sudo docker compose up -d - - sudo docker image prune -a -f + # 공통 컨테이너 실행 + sudo docker compose -f docker-compose.common.yml up -d + + # 배포 로직 실행 + ./deploy.sh \ No newline at end of file From 64bc0f64b495c49338f22430538af50946adf321 Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Wed, 5 Nov 2025 11:02:48 +0900 Subject: [PATCH 11/13] =?UTF-8?q?feat=20:=20healthCheck=20API=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20swagger=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task/controller/TaskController.java | 164 +++++++++--------- .../growin/global/config/SwaggerConfig.java | 37 ++++ .../controller/HealthCheckController.java | 7 +- 3 files changed, 124 insertions(+), 84 deletions(-) create mode 100644 src/main/java/ita/growin/global/config/SwaggerConfig.java diff --git a/src/main/java/ita/growin/domain/task/controller/TaskController.java b/src/main/java/ita/growin/domain/task/controller/TaskController.java index 2014210..a5db466 100644 --- a/src/main/java/ita/growin/domain/task/controller/TaskController.java +++ b/src/main/java/ita/growin/domain/task/controller/TaskController.java @@ -1,82 +1,82 @@ -package ita.growin.domain.task.controller; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import ita.growin.domain.task.dto.CreateTaskRequestDto; -import ita.growin.domain.task.dto.UpdateTaskRequestDto; -import ita.growin.domain.task.service.TaskService; -import ita.growin.global.response.APIResponse; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.web.PageableDefault; -import org.springframework.lang.Nullable; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/api/v1/tasks") -@Tag(name = "할 일", description = "할 일 관련 API") -public class TaskController { - - private final TaskService taskService; - - public TaskController(TaskService taskService) { - this.taskService = taskService; - } - - @PostMapping - @Operation(summary = "할 일 생성 API") - public APIResponse createTask( - @RequestParam("eventId") @Nullable Long eventId, - @RequestBody CreateTaskRequestDto req) { - taskService.createTask(eventId, req); - return APIResponse.success(); - } - - @PatchMapping("/{taskId}") - @Operation(summary = "할 일 수정 API") - public APIResponse updateTask( - @PathVariable Long taskId, - @RequestBody UpdateTaskRequestDto req) { - taskService.updateTask(taskId, req); - return APIResponse.success(); - } - - @DeleteMapping("/{taskId}") - @Operation(summary = "할 일 삭제 API") - public APIResponse deleteTask( - @RequestParam("eventId") @Nullable Long eventId, - @PathVariable Long taskId) { - taskService.deleteTask(eventId, taskId); - return APIResponse.success(); - } - - @GetMapping("/{taskId}") - @Operation(summary = "할 일 단건 조회") - public APIResponse getTask(@PathVariable Long taskId) { - return APIResponse.success(taskService.getTask(taskId)); - } - - @GetMapping("/event/{eventId}/tasks") - @Operation(summary = "일정 내 할 일 조회") - public APIResponse> getTasks( - @PathVariable Long eventId, - @PageableDefault(size = 20) Pageable pageable) { - return APIResponse.success(taskService.getTasks(eventId, pageable)); - } - - @GetMapping("/tasks/today") - @Operation(summary = "오늘 할 일 조회") - public APIResponse getTodayTasks( - @PageableDefault(size = 20) Pageable pageable - ) { - return APIResponse.success(taskService.getTodayTasks(pageable)); - } - - @GetMapping("/tasks/someday") - @Operation(summary = "언젠가 할 일 조회") - public APIResponse getSomedayTasks( - @PageableDefault(size = 20) Pageable pageable - ) { - return APIResponse.success(taskService.getSomedayTasks(pageable)); - } -} +//package ita.growin.domain.task.controller; +// +//import io.swagger.v3.oas.annotations.Operation; +//import io.swagger.v3.oas.annotations.tags.Tag; +//import ita.growin.domain.task.dto.CreateTaskRequestDto; +//import ita.growin.domain.task.dto.UpdateTaskRequestDto; +//import ita.growin.domain.task.service.TaskService; +//import ita.growin.global.response.APIResponse; +//import org.springframework.data.domain.Pageable; +//import org.springframework.data.domain.Slice; +//import org.springframework.data.web.PageableDefault; +//import org.springframework.lang.Nullable; +//import org.springframework.web.bind.annotation.*; +// +//@RestController +//@RequestMapping("/api/v1/tasks") +//@Tag(name = "할 일", description = "할 일 관련 API") +//public class TaskController { +// +// private final TaskService taskService; +// +// public TaskController(TaskService taskService) { +// this.taskService = taskService; +// } +// +// @PostMapping +// @Operation(summary = "할 일 생성 API") +// public APIResponse createTask( +// @RequestParam("eventId") @Nullable Long eventId, +// @RequestBody CreateTaskRequestDto req) { +// taskService.createTask(eventId, req); +// return APIResponse.success(); +// } +// +// @PatchMapping("/{taskId}") +// @Operation(summary = "할 일 수정 API") +// public APIResponse updateTask( +// @PathVariable Long taskId, +// @RequestBody UpdateTaskRequestDto req) { +// taskService.updateTask(taskId, req); +// return APIResponse.success(); +// } +// +// @DeleteMapping("/{taskId}") +// @Operation(summary = "할 일 삭제 API") +// public APIResponse deleteTask( +// @RequestParam("eventId") @Nullable Long eventId, +// @PathVariable Long taskId) { +// taskService.deleteTask(eventId, taskId); +// return APIResponse.success(); +// } +// +// @GetMapping("/{taskId}") +// @Operation(summary = "할 일 단건 조회") +// public APIResponse getTask(@PathVariable Long taskId) { +// return APIResponse.success(taskService.getTask(taskId)); +// } +// +// @GetMapping("/event/{eventId}/tasks") +// @Operation(summary = "일정 내 할 일 조회") +// public APIResponse> getTasks( +// @PathVariable Long eventId, +// @PageableDefault(size = 20) Pageable pageable) { +// return APIResponse.success(taskService.getTasks(eventId, pageable)); +// } +// +// @GetMapping("/tasks/today") +// @Operation(summary = "오늘 할 일 조회") +// public APIResponse getTodayTasks( +// @PageableDefault(size = 20) Pageable pageable +// ) { +// return APIResponse.success(taskService.getTodayTasks(pageable)); +// } +// +// @GetMapping("/tasks/someday") +// @Operation(summary = "언젠가 할 일 조회") +// public APIResponse getSomedayTasks( +// @PageableDefault(size = 20) Pageable pageable +// ) { +// return APIResponse.success(taskService.getSomedayTasks(pageable)); +// } +//} diff --git a/src/main/java/ita/growin/global/config/SwaggerConfig.java b/src/main/java/ita/growin/global/config/SwaggerConfig.java new file mode 100644 index 0000000..db83bed --- /dev/null +++ b/src/main/java/ita/growin/global/config/SwaggerConfig.java @@ -0,0 +1,37 @@ +package ita.growin.global.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .addSecurityItem(new SecurityRequirement().addList("BearerAuth")) + .components( + new Components() + .addSecuritySchemes( + "BearerAuth", + new SecurityScheme() + .name("Authorization") + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + .in(SecurityScheme.In.HEADER))) + .info( + new Info() + .title("Growin Swagger Page") + .license( + new License() + .name("growin API 명세서") + .url("http://localhost:8080"))); + } +} diff --git a/src/main/java/ita/growin/global/health/controller/HealthCheckController.java b/src/main/java/ita/growin/global/health/controller/HealthCheckController.java index 481870e..1d2e21d 100644 --- a/src/main/java/ita/growin/global/health/controller/HealthCheckController.java +++ b/src/main/java/ita/growin/global/health/controller/HealthCheckController.java @@ -3,15 +3,18 @@ import ita.growin.global.exception.BusinessException; import ita.growin.global.exception.errorcode.BusinessErrorCode; import ita.growin.global.response.APIResponse; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController +@RequestMapping("/test") public class HealthCheckController { @GetMapping("/health") - public APIResponse test() { - return APIResponse.success("test"); + public ResponseEntity test() { + return ResponseEntity.ok("UP"); } @GetMapping("/business-error") From 89d9bb2631e1a49c15099157b34c4aef428cb2d3 Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Wed, 5 Nov 2025 11:04:59 +0900 Subject: [PATCH 12/13] =?UTF-8?q?fix=20:=20healthCheck=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../health/controller/HealthCheckController.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/ita/growin/global/health/controller/HealthCheckController.java b/src/main/java/ita/growin/global/health/controller/HealthCheckController.java index 1d2e21d..06846f1 100644 --- a/src/main/java/ita/growin/global/health/controller/HealthCheckController.java +++ b/src/main/java/ita/growin/global/health/controller/HealthCheckController.java @@ -5,18 +5,21 @@ import ita.growin.global.response.APIResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/test") public class HealthCheckController { - @GetMapping("/health") - public ResponseEntity test() { + @GetMapping("test/health") + public ResponseEntity health() { return ResponseEntity.ok("UP"); } + @GetMapping("/health") + public APIResponse test() { + return APIResponse.success("test"); + } + @GetMapping("/business-error") public APIResponse businessError() { throw BusinessException.of(BusinessErrorCode.MEMBER_NOT_FOUND); From 7286d84f169e260f51fc98527b528f213f5ab239 Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Wed, 5 Nov 2025 11:23:10 +0900 Subject: [PATCH 13/13] =?UTF-8?q?fix=20:=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd.yml | 2 +- deploy.sh | 16 ++++++++-------- docker/docker-compose.common.yml | 4 ++++ .../health/controller/HealthCheckController.java | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index dda0860..650934b 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -44,7 +44,7 @@ jobs: echo $GHCR_TOKEN | docker login ghcr.io -u $GHCR_USERNAME --password-stdin # 공통 컨테이너 실행 - sudo docker compose -f docker-compose.common.yml up -d + sudo docker compose -f docker/docker-compose.common.yml up -d # 배포 로직 실행 ./deploy.sh \ No newline at end of file diff --git a/deploy.sh b/deploy.sh index 383f561..c8d1928 100644 --- a/deploy.sh +++ b/deploy.sh @@ -8,15 +8,15 @@ NGINX_CONF=/home/ubuntu/growin/nginx/default.conf echo "deploy start" -if ! docker ps --format '{{.Names}}' | grep -q "${APP_NAME}_blue" && \ - ! docker ps --format '{{.Names}}' | grep -q "${APP_NAME}_green"; then +if ! docker ps --format '{{.Names}}' | grep -q "${APP_NAME}-blue" && \ + ! docker ps --format '{{.Names}}' | grep -q "${APP_NAME}-green"; then echo "First deployment detected — starting blue container..." - docker-compose -f docker-compose.blue.yml up -d + docker compose -f docker/docker-compose.blue.yml up -d exit 0 fi -if docker ps | grep -q "${APP_NAME}-blue"; then +if docker ps --format '{{.Names}}' | grep -q "${APP_NAME}-blue"; then CURRENT="blue" NEXT="green" CURRENT_PORT=$BLUE_PORT @@ -33,7 +33,7 @@ ehco "Current Container : $CURRENT" echo "Next Container : $NEXT" echo "deploy $NEXT Container" -docker-compose -f docker-compose.${NEXT}.yml up -d +docker compose -f docker/docker-compose.${NEXT}.yml up -d echo "running health check" @@ -52,7 +52,7 @@ done # 실행 실패 시 -> 롤백 진행 후 종료 if [ "$success" = false ]; then echo "Health check failed! Rolling back..." - docker-compose -f docker-compose.${NEXT}.yml down + docker compose -f docker/docker-compose.${NEXT}.yml down exit 1 fi @@ -65,8 +65,8 @@ echo "if success, switch nginx conf and stop old container" # Stop old container echo "==> Stopping old container ${APP_NAME}_${CURRENT}" -docker stop ${APP_NAME}_${CURRENT} || true -docker rm ${APP_NAME}_${CURRENT} || true +docker stop ${APP_NAME}-${CURRENT} || true +docker rm ${APP_NAME}-${CURRENT} || true echo "Cleaning unused images" docker image prune -f >/dev/null 2>&1 diff --git a/docker/docker-compose.common.yml b/docker/docker-compose.common.yml index 8b6219d..4f82811 100644 --- a/docker/docker-compose.common.yml +++ b/docker/docker-compose.common.yml @@ -12,6 +12,8 @@ services: - ./data/certbot/conf:/etc/letsencrypt - ./data/certbot/www:/var/www/certbot restart: always + networks: + - growin-network certbot: container_name: certbot @@ -20,6 +22,8 @@ services: - ./data/certbot/conf:/etc/letsencrypt - ./data/certbot/www:/var/www/certbot entrypoint: /bin/sh -c "trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;" + networks: + - growin-network networks: app-network: diff --git a/src/main/java/ita/growin/global/health/controller/HealthCheckController.java b/src/main/java/ita/growin/global/health/controller/HealthCheckController.java index 06846f1..62852f8 100644 --- a/src/main/java/ita/growin/global/health/controller/HealthCheckController.java +++ b/src/main/java/ita/growin/global/health/controller/HealthCheckController.java @@ -10,7 +10,7 @@ @RestController public class HealthCheckController { - @GetMapping("test/health") + @GetMapping("/test/health") public ResponseEntity health() { return ResponseEntity.ok("UP"); }