Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,10 @@ void createBanner(
@DeleteMapping("/{bannerId}")
void deleteBanner(@PathVariable("bannerId") Long bannerId);

@Operation(summary = "랭킹 배너 재생성 API (임시)")
@ApiResponse(responseCode = "204", description = "랭킹 배너 재생성 성공")
@ResponseStatus(HttpStatus.NO_CONTENT)
@SecurityRequirement(name = "AccessToken")
Comment on lines +55 to +57
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

POST 재생성 엔드포인트의 상태 코드는 201로 맞춰주세요.

Line [56]이 204 NO_CONTENT로 선언되어 있어 저장소 규칙과 충돌합니다.

제안 diff
-    `@ApiResponse`(responseCode = "204", description = "랭킹 배너 재생성 성공")
-    `@ResponseStatus`(HttpStatus.NO_CONTENT)
+    `@ApiResponse`(responseCode = "201", description = "랭킹 배너 재생성 성공")
+    `@ResponseStatus`(HttpStatus.CREATED)

As per coding guidelines, HTTP status codes: POST returns 201 Created, GET returns 200 OK, PUT/PATCH/DELETE returns 204 No Content 규칙을 따라야 합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@ApiResponse(responseCode = "204", description = "랭킹 배너 재생성 성공")
@ResponseStatus(HttpStatus.NO_CONTENT)
@SecurityRequirement(name = "AccessToken")
`@ApiResponse`(responseCode = "201", description = "랭킹 배너 재생성 성공")
`@ResponseStatus`(HttpStatus.CREATED)
`@SecurityRequirement`(name = "AccessToken")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ddingdong/ddingdongBE/domain/banner/api/AdminBannerApi.java`
around lines 55 - 57, The POST endpoint in AdminBannerApi currently declares a
204 status via `@ApiResponse`(responseCode = "204") and
`@ResponseStatus`(HttpStatus.NO_CONTENT); change both to return 201 by updating
`@ApiResponse`(responseCode = "201", description = "...") and
`@ResponseStatus`(HttpStatus.CREATED) so the POST follows the guideline (HTTP POST
-> 201 Created); locate these annotations above the method that performs the
ranking banner recreation in AdminBannerApi.java and adjust the values
accordingly.

@PostMapping("/ranking/regenerate")
void regenerateRankingBanners();
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import ddingdong.ddingdongBE.domain.banner.controller.dto.request.CreateBannerRequest;
import ddingdong.ddingdongBE.domain.banner.controller.dto.response.AdminBannerListResponse;
import ddingdong.ddingdongBE.domain.banner.service.FacadeAdminBannerService;
import ddingdong.ddingdongBE.domain.banner.service.FacadeRankingBannerService;
import ddingdong.ddingdongBE.domain.user.entity.User;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand All @@ -15,6 +16,7 @@
public class AdminBannerController implements AdminBannerApi {

private final FacadeAdminBannerService facadeAdminBannerService;
private final FacadeRankingBannerService facadeRankingBannerService;

@Override
public void createBanner(PrincipalDetails principalDetails, CreateBannerRequest request) {
Expand All @@ -34,4 +36,8 @@ public void deleteBanner(Long bannerId) {
facadeAdminBannerService.delete(bannerId);
}

@Override
public void regenerateRankingBanners() {
facadeRankingBannerService.regenerateLatestRankingBanners();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GraphicsEnvironment;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.TextAttribute;
Expand Down Expand Up @@ -190,13 +191,18 @@ private Font createStyledFont(Font baseFont, int style, float size, float tracki

private Font loadFont(String path) {
try (InputStream fontStream = getClass().getClassLoader().getResourceAsStream(path)) {
if (fontStream != null) {
return Font.createFont(Font.TRUETYPE_FONT, fontStream);
if (fontStream == null) {
throw new BannerImageGenerationException();
}
Font font = Font.createFont(Font.TRUETYPE_FONT, fontStream);
GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font);
return font;
} catch (BannerImageGenerationException e) {
throw e;
} catch (Exception e) {
log.warn("커스텀 폰트 로드 실패 ({}), 기본 폰트 사용: {}", path, e.getMessage());
log.error("커스텀 폰트 로드 실패 ({}): {}", path, e.getMessage());
throw new BannerImageGenerationException();
Comment on lines +200 to +204
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

예외 로깅에서 스택트레이스가 유실됩니다.

현재는 메시지만 기록되어 폰트 로드 실패 원인 추적이 어렵습니다. 동시에 재던지기용 catch (BannerImageGenerationException e)는 제거해도 동작이 같습니다.

수정 예시
-        } catch (BannerImageGenerationException e) {
-            throw e;
-        } catch (Exception e) {
-            log.error("커스텀 폰트 로드 실패 ({}): {}", path, e.getMessage());
+        } catch (Exception e) {
+            if (e instanceof BannerImageGenerationException) {
+                throw (BannerImageGenerationException) e;
+            }
+            log.error("커스텀 폰트 로드 실패 ({})", path, e);
             throw new BannerImageGenerationException();
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/ddingdong/ddingdongBE/domain/banner/service/BannerImageGenerator.java`
around lines 200 - 204, The catch blocks in BannerImageGenerator swallowing the
exception lose the stacktrace and the first catch for
BannerImageGenerationException is redundant; remove the separate catch
(BannerImageGenerationException e) block, and in the remaining catch (Exception
e) include the Throwable when logging (use log.error with the exception
parameter) and rethrow a new or the original BannerImageGenerationException as
appropriate so the root cause is preserved; update the log.error call that
references path to pass e as the last argument (or include e) and
construct/throw BannerImageGenerationException with the original exception as
its cause.

}
return new Font("SansSerif", Font.BOLD, 36);
}

private byte[] toPngBytes(BufferedImage image) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@
public interface FacadeRankingBannerService {

void createRankingBanners(List<FeedMonthlyRanking> firstPlaceRankings);

void regenerateLatestRankingBanners();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import ddingdong.ddingdongBE.domain.club.entity.Club;
import ddingdong.ddingdongBE.domain.club.service.ClubService;
import ddingdong.ddingdongBE.domain.feed.entity.FeedMonthlyRanking;
import ddingdong.ddingdongBE.domain.feed.repository.FeedMonthlyRankingRepository;
import ddingdong.ddingdongBE.domain.filemetadata.entity.DomainType;
import ddingdong.ddingdongBE.domain.filemetadata.entity.FileMetaData;
import ddingdong.ddingdongBE.domain.filemetadata.entity.FileStatus;
Expand All @@ -14,6 +15,7 @@
import ddingdong.ddingdongBE.file.service.dto.query.UploadedFileUrlQuery;
import java.awt.image.BufferedImage;
import java.net.URI;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import javax.imageio.ImageIO;
Expand All @@ -36,6 +38,7 @@ public class FacadeRankingBannerServiceImpl implements FacadeRankingBannerServic
private final FileMetaDataService fileMetaDataService;
private final S3FileService s3FileService;
private final BannerImageGenerator bannerImageGenerator;
private final FeedMonthlyRankingRepository feedMonthlyRankingRepository;

@Override
@Transactional
Expand All @@ -47,6 +50,25 @@ public void createRankingBanners(List<FeedMonthlyRanking> firstPlaceRankings) {
}
}

@Override
@Transactional
public void regenerateLatestRankingBanners() {
LocalDate lastMonth = LocalDate.now().minusMonths(1);
List<FeedMonthlyRanking> firstPlaceRankings =
feedMonthlyRankingRepository.findAllByTargetYearAndTargetMonthAndRanking(
lastMonth.getYear(), lastMonth.getMonthValue(), 1);

if (firstPlaceRankings.isEmpty()) {
log.info("재생성할 랭킹 1위 동아리가 없습니다. year={}, month={}",
lastMonth.getYear(), lastMonth.getMonthValue());
return;
}

createRankingBanners(firstPlaceRankings);
log.info("랭킹 배너 재생성 완료. year={}, month={}, count={}",
lastMonth.getYear(), lastMonth.getMonthValue(), firstPlaceRankings.size());
}

private void deleteExistingRankingBanners() {
List<Banner> existingBanners = bannerService.getAllByBannerType(BannerType.FEED_RANKING);
for (Banner banner : existingBanners) {
Expand Down
Loading