diff --git a/.github/workflows/cd-develop.yml b/.github/workflows/cd-develop.yml index f4251d64..48881ed1 100644 --- a/.github/workflows/cd-develop.yml +++ b/.github/workflows/cd-develop.yml @@ -69,16 +69,19 @@ jobs: run: | echo "๐Ÿงน Cleaning up all existing containers" - # Stop and remove specific containers + # Stop and remove specific containers (๋กœ๊ทธ๋Š” ๋ณผ๋ฅจ์— ๋ณด์กด๋จ) sudo docker stop github-actions-demo || true sudo docker rm github-actions-demo || true sudo docker stop elasticsearch || true sudo docker rm elasticsearch || true + echo "๐Ÿ“‹ Ensuring log directory exists on host" + sudo mkdir -p /var/log/wayble + sudo chmod 755 /var/log/wayble - echo "๐Ÿงฏ Cleaning up unused Docker networks" - sudo docker system prune -f || true + echo "๐Ÿงฏ Cleaning up unused Docker networks (excluding volumes)" + sudo docker system prune -f --volumes=false || true - name: Create Docker network if not exists run: | @@ -184,6 +187,18 @@ jobs: exit 1 fi + # ๋กœ๊ทธ ํŒŒ์ผ ์ƒํƒœ ํ™•์ธ + echo "=== Log Directory Status ===" + ls -la /var/log/wayble/ || echo "Log directory not found" + + if [ -f "/var/log/wayble/wayble-error.log" ]; then + echo "โœ… Error log file exists" + echo "๐Ÿ“Š Error log file size: $(du -h /var/log/wayble/wayble-error.log | cut -f1)" + echo "๐Ÿ“… Last modified: $(stat -c %y /var/log/wayble/wayble-error.log)" + else + echo "โ„น๏ธ No error log file yet (normal for new deployment)" + fi + # โœ… ๋ฐฐํฌ ์„ฑ๊ณต ์•Œ๋ฆผ (Discord) - name: Send success webhook to Discord if: success() @@ -204,7 +219,6 @@ jobs: - # on: #์ด ์›Œํฌํ”Œ๋กœ์šฐ๊ฐ€ ์–ธ์ œ ์‹คํ–‰๋ ์ง€ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ •์˜ํ•จ. # pull_request: # types : [closed] #๋ˆ„๊ตฐ๊ฐ€๊ฐ€ Pull request๋ฅผ ๋‹ซ์•˜์„ ๋•Œ ์‹คํ–‰๋จ. diff --git a/Dockerfile b/Dockerfile index ba724222..fb3ffbf6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,12 @@ FROM openjdk:17 # ์ธ์ž ์„ค์ • - JAR_File ARG JAR_FILE=build/libs/*.jar +# ๋กœ๊ทธ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ ๋ฐ ๊ถŒํ•œ ์„ค์ • +RUN mkdir -p /app/logs && chmod 755 /app/logs + +# ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ • +WORKDIR /app + # jar ํŒŒ์ผ ๋ณต์ œ COPY ${JAR_FILE} app.jar diff --git a/src/main/java/com/wayble/server/common/exception/GlobalExceptionHandler.java b/src/main/java/com/wayble/server/common/exception/GlobalExceptionHandler.java index ce6ace69..22288ba5 100644 --- a/src/main/java/com/wayble/server/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/wayble/server/common/exception/GlobalExceptionHandler.java @@ -38,7 +38,7 @@ public class GlobalExceptionHandler { @ExceptionHandler(ApplicationException.class) public ResponseEntity handleApplicationException(ApplicationException e, WebRequest request) { - // ๋น„์ฆˆ๋‹ˆ์Šค ์˜ˆ์™ธ ๋กœ๊ทธ ๊ธฐ๋ก (๊ฐ„๊ฒฐํ•˜๊ฒŒ) + String path = ((ServletWebRequest) request).getRequest().getRequestURI(); String method = ((ServletWebRequest) request).getRequest().getMethod(); @@ -47,36 +47,31 @@ public ResponseEntity handleApplicationException(ApplicationExce CommonResponse commonResponse = CommonResponse.error(e.getErrorCase()); - HttpStatus status = HttpStatus.valueOf(e.getErrorCase().getHttpStatusCode()); - //sendToDiscord(e, request, status); - return ResponseEntity .status(e.getErrorCase().getHttpStatusCode()) .body(commonResponse); } - @ExceptionHandler(value = MethodArgumentNotValidException.class) - public ResponseEntity handleValidException(BindingResult bindingResult, - MethodArgumentNotValidException ex, - WebRequest request) { - String message = bindingResult.getAllErrors().get(0).getDefaultMessage(); + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidException(MethodArgumentNotValidException ex, WebRequest request) { + String message = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage(); // ์—๋Ÿฌ ๋กœ๊ทธ ๊ธฐ๋ก String path = ((ServletWebRequest) request).getRequest().getRequestURI(); String method = ((ServletWebRequest) request).getRequest().getMethod(); String errorLocation = getErrorLocation(ex); - log.error("Validation Exception ๋ฐœ์ƒ - Method: {}, Path: {}, Message: {}, Location: {}", - method, path, message, errorLocation, ex); + log.warn("Validation Exception - Method: {}, Path: {}, Message: {}, Location: {}", + method, path, message, errorLocation); CommonResponse commonResponse = CommonResponse.error(400, message); - - sendToDiscord(ex, request, HttpStatus.BAD_REQUEST); + return ResponseEntity .status(HttpStatus.BAD_REQUEST) .body(commonResponse); } + /** * ๋ชจ๋“  ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ */ @@ -107,6 +102,12 @@ private void sendToDiscord(Exception ex, WebRequest request, HttpStatus status) return; } + // ํŠน์ • ์˜ˆ์™ธ ํƒ€์ž… ๋ฐ ๊ฒฝ๋กœ์— ๋Œ€ํ•œ Discord ์•Œ๋ฆผ ์ œ์™ธ + if (shouldSkipDiscordNotification(ex, path)) { + log.debug("Discord ์•Œ๋ฆผ ์ œ์™ธ - Exception: {}, Path: {}", ex.getClass().getSimpleName(), path); + return; + } + // Embed ํ•„๋“œ ๊ตฌ์„ฑ DiscordWebhookPayload.Embed embed = new DiscordWebhookPayload.Embed( "๐Ÿšจ ์„œ๋ฒ„ ์—๋Ÿฌ ๋ฐœ์ƒ", @@ -136,6 +137,108 @@ private void sendToDiscord(Exception ex, WebRequest request, HttpStatus status) } } + /** + * Discord ์•Œ๋ฆผ์„ ๋ณด๋‚ด์ง€ ์•Š์„ ์˜ˆ์™ธ์ธ์ง€ ํŒ๋‹จ + */ + private boolean shouldSkipDiscordNotification(Exception ex, String path) { + String exceptionName = ex.getClass().getSimpleName(); + String message = ex.getMessage(); + + // 1. NoResourceFoundException ์ œ์™ธ (static resource ์š”์ฒญ) + if ("NoResourceFoundException".equals(exceptionName)) { + return true; + } + + // 2. ํŠน์ • ๊ฒฝ๋กœ ํŒจํ„ด ์ œ์™ธ + if (isIgnoredPath(path)) { + return true; + } + + // 3. ๋ด‡์ด๋‚˜ ํฌ๋กค๋Ÿฌ ์š”์ฒญ์œผ๋กœ ์ธํ•œ ์—๋Ÿฌ ์ œ์™ธ + if (isBotOrCrawlerRequest(message)) { + return true; + } + + // 4. ๊ธฐํƒ€ ๋ถˆํ•„์š”ํ•œ ์˜ˆ์™ธ๋“ค + if (isIgnoredException(exceptionName, message)) { + return true; + } + + return false; + } + + /** + * ๋ฌด์‹œํ•  ๊ฒฝ๋กœ์ธ์ง€ ํ™•์ธ + */ + private boolean isIgnoredPath(String path) { + String[] ignoredPaths = { + "/favicon.ico", + "/index.html", + "/robots.txt", + "/sitemap.xml", + "/apple-touch-icon", + "/.well-known/", + "/wp-admin/", + "/admin/", + "/phpmyadmin/", + "/xmlrpc.php", + "/.env", + "/config.php" + }; + + for (String ignoredPath : ignoredPaths) { + if (path.contains(ignoredPath)) { + return true; + } + } + + return false; + } + + /** + * ๋ด‡์ด๋‚˜ ํฌ๋กค๋Ÿฌ ์š”์ฒญ์ธ์ง€ ํ™•์ธ + */ + private boolean isBotOrCrawlerRequest(String message) { + if (message == null) return false; + + String[] botIndicators = { + "No static resource", + "Could not resolve view", + "favicon", + "robots.txt" + }; + + for (String indicator : botIndicators) { + if (message.contains(indicator)) { + return true; + } + } + + return false; + } + + /** + * ๋ฌด์‹œํ•  ์˜ˆ์™ธ์ธ์ง€ ํ™•์ธ + */ + private boolean isIgnoredException(String exceptionName, String message) { + // ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ ์ข…๋ฃŒ ๊ด€๋ จ + if ("ClientAbortException".equals(exceptionName) || + "BrokenPipeException".equals(exceptionName)) { + return true; + } + + // ํƒ€์ž„์•„์›ƒ ๊ด€๋ จ (๋„ˆ๋ฌด ๋นˆ๋ฒˆํ•œ ๊ฒฝ์šฐ) + if (message != null && ( + message.contains("Connection timed out") || + message.contains("Read timed out") || + message.contains("Connection reset") + )) { + return true; + } + + return false; + } + /** * ์˜ˆ์™ธ์˜ ์ŠคํƒํŠธ๋ ˆ์ด์Šค์—์„œ ์‹ค์ œ ์—๋Ÿฌ ๋ฐœ์ƒ ์œ„์น˜๋ฅผ ์ถ”์ถœ */ diff --git a/src/main/java/com/wayble/server/explore/controller/WaybleZoneRecommendController.java b/src/main/java/com/wayble/server/explore/controller/WaybleZoneRecommendController.java index a757b94d..886f7d23 100644 --- a/src/main/java/com/wayble/server/explore/controller/WaybleZoneRecommendController.java +++ b/src/main/java/com/wayble/server/explore/controller/WaybleZoneRecommendController.java @@ -6,6 +6,7 @@ import com.wayble.server.explore.service.WaybleZoneRecommendService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -24,8 +25,9 @@ public CommonResponse> getWaybleZonePersona @Valid @ModelAttribute WaybleZoneRecommendConditionDto conditionDto, @RequestParam(name = "size", defaultValue = "1") int size) { + Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); List result = waybleZoneRecommendService.getWaybleZonePersonalRecommend( - conditionDto.userId(), + userId, conditionDto.latitude(), conditionDto.longitude(), size diff --git a/src/main/java/com/wayble/server/explore/dto/recommend/WaybleZoneRecommendConditionDto.java b/src/main/java/com/wayble/server/explore/dto/recommend/WaybleZoneRecommendConditionDto.java index a4fa46cd..30f7333c 100644 --- a/src/main/java/com/wayble/server/explore/dto/recommend/WaybleZoneRecommendConditionDto.java +++ b/src/main/java/com/wayble/server/explore/dto/recommend/WaybleZoneRecommendConditionDto.java @@ -15,9 +15,6 @@ public record WaybleZoneRecommendConditionDto( @DecimalMin(value = "-180.0", message = "๊ฒฝ๋„๋Š” -180.0 ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.") @DecimalMax(value = "180.0", message = "๊ฒฝ๋„๋Š” 180.0 ์ดํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.") @NotNull(message = "๊ฒฝ๋„ ์ž…๋ ฅ์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.") - Double longitude, - - @NotNull(message = "์œ ์ € ID๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.") - Long userId + Double longitude ) { } diff --git a/src/main/java/com/wayble/server/user/controller/UserPlaceController.java b/src/main/java/com/wayble/server/user/controller/UserPlaceController.java index a944dc54..d17a09ea 100644 --- a/src/main/java/com/wayble/server/user/controller/UserPlaceController.java +++ b/src/main/java/com/wayble/server/user/controller/UserPlaceController.java @@ -18,13 +18,12 @@ import java.util.List; @RestController -@RequestMapping("/api/v1/users/{userId}/places") +@RequestMapping("/api/v1/users/places") @RequiredArgsConstructor public class UserPlaceController { private final UserPlaceService userPlaceService; - @PostMapping @Operation(summary = "์œ ์ € ์žฅ์†Œ ์ €์žฅ", description = "์œ ์ €๊ฐ€ ์›จ์ด๋ธ”์กด์„ ์žฅ์†Œ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.") @ApiResponses({ @@ -34,18 +33,9 @@ public class UserPlaceController { @ApiResponse(responseCode = "403", description = "๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.") }) public CommonResponse saveUserPlace( - @PathVariable Long userId, @RequestBody @Valid UserPlaceRequestDto request ) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (!(authentication.getPrincipal() instanceof Long)) { - throw new ApplicationException(UserErrorCase.FORBIDDEN); - } - Long tokenUserId = (Long) authentication.getPrincipal(); - if (!userId.equals(tokenUserId)) { - throw new ApplicationException(UserErrorCase.FORBIDDEN); - } - + Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); userPlaceService.saveUserPlace(userId, request); // userId ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊น€ return CommonResponse.success("์žฅ์†Œ๊ฐ€ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); } @@ -61,16 +51,8 @@ public CommonResponse saveUserPlace( @ApiResponse(responseCode = "403", description = "๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.") }) public CommonResponse> getUserPlaces( - @PathVariable Long userId ) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (!(authentication.getPrincipal() instanceof Long)) { - throw new ApplicationException(UserErrorCase.FORBIDDEN); - } - Long tokenUserId = (Long) authentication.getPrincipal(); - if (!userId.equals(tokenUserId)) { - throw new ApplicationException(UserErrorCase.FORBIDDEN); - } + Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); List places = userPlaceService.getUserPlaces(userId); return CommonResponse.success(places); } diff --git a/src/main/java/com/wayble/server/user/dto/UserPlaceListResponseDto.java b/src/main/java/com/wayble/server/user/dto/UserPlaceListResponseDto.java index 73f155b9..9a8d79ee 100644 --- a/src/main/java/com/wayble/server/user/dto/UserPlaceListResponseDto.java +++ b/src/main/java/com/wayble/server/user/dto/UserPlaceListResponseDto.java @@ -6,17 +6,17 @@ @Builder public record UserPlaceListResponseDto( - Long place_id, + Long placeId, String title, - WaybleZoneDto wayble_zone + WaybleZoneDto waybleZone ) { @Builder public record WaybleZoneDto( - Long wayble_zone_id, + Long waybleZoneId, String name, String category, double rating, String address, - String image_url + String imageUrl ) {} } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/user/service/UserPlaceService.java b/src/main/java/com/wayble/server/user/service/UserPlaceService.java index 6581e153..8200715e 100644 --- a/src/main/java/com/wayble/server/user/service/UserPlaceService.java +++ b/src/main/java/com/wayble/server/user/service/UserPlaceService.java @@ -78,16 +78,16 @@ public List getUserPlaces(Long userId) { String imageUrl = waybleZone.getMainImageUrl(); return UserPlaceListResponseDto.builder() - .place_id(userPlace.getId()) + .placeId(userPlace.getId()) .title(userPlace.getTitle()) - .wayble_zone( + .waybleZone( UserPlaceListResponseDto.WaybleZoneDto.builder() - .wayble_zone_id(waybleZone.getId()) + .waybleZoneId(waybleZone.getId()) .name(waybleZone.getZoneName()) .category(waybleZone.getZoneType().toString()) .rating(waybleZone.getRating()) .address(waybleZone.getAddress().toFullAddress()) - .image_url(imageUrl) + .imageUrl(imageUrl) .build() ) .build(); diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index ef41b877..246c2b0b 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -34,7 +34,7 @@ 50MB 90 1GB - true + false [%d{yyyy-MM-dd HH:mm:ss}] [%thread] %-5level %logger{36} - %msg%n%ex{2}