Skip to content
Open
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
13 changes: 13 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: PR Agent
on:
pull_request:
types: [opened, synchronize]
jobs:
pr_agent_job:
runs-on: ubuntu-latest
steps:
- name: PR Agent action step
uses: Codium-ai/pr-agent@main
Comment on lines +9 to +10
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

์•ก์…˜ ๋ฒ„์ „์„ ํŠน์ • ์ปค๋ฐ‹ SHA๋กœ ๊ณ ์ •ํ•˜์„ธ์š”.

@main ๋ธŒ๋žœ์น˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ญ์ƒ ์ตœ์‹  ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜์–ด ๊ณต๊ธ‰๋ง ๋ณด์•ˆ ์œ„ํ—˜์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. GitHub Actions ๋ณด์•ˆ ๋ชจ๋ฒ” ์‚ฌ๋ก€์— ๋”ฐ๋ผ third-party ์•ก์…˜์€ ํŠน์ • ์ปค๋ฐ‹ SHA๋กœ ๊ณ ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ๊ถŒ์žฅ ์ˆ˜์ • ์‚ฌํ•ญ
-      - name: PR Agent action step
-        uses: Codium-ai/pr-agent@main
+      - name: PR Agent action step
+        uses: Codium-ai/pr-agent@<specific-commit-sha>

์ตœ์‹  ๋ฆด๋ฆฌ์Šค์˜ ์ปค๋ฐ‹ SHA๋ฅผ ํ™•์ธํ•˜๋ ค๋ฉด ๋‹ค์Œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”:

#!/bin/bash
# Codium-ai/pr-agent ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์˜ ์ตœ์‹  ๋ฆด๋ฆฌ์Šค ํƒœ๊ทธ ํ™•์ธ
gh api repos/Codium-ai/pr-agent/releases/latest --jq '.tag_name'

Committable suggestion skipped: line range outside the PR's diff.

๐Ÿค– Prompt for AI Agents
.github/workflows/main.yml lines 9-10: the workflow currently uses the
third-party action via "uses: Codium-ai/pr-agent@main"; replace the branch ref
with a specific commit SHA to pin the action and mitigate supply-chain risk.
Find the commit SHA for the desired release (via the repo's latest release or
GitHub UI), then update the uses line to "uses: Codium-ai/pr-agent@<COMMIT_SHA>"
so the workflow always runs that exact commit.

env:
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
GITHUB_TOKEN: ${{ secrets.G_TOKEN }}
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸก Minor

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

# ์›Œํฌํ”Œ๋กœ์šฐ ํŒŒ์ผ ์ „์ฒด ๋‚ด์šฉ ํ™•์ธ
cat -n .github/workflows/main.yml

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 487


๐Ÿ Script executed:

# ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์—์„œ G_TOKEN ์ฐธ์กฐ ๊ฒ€์ƒ‰
rg "G_TOKEN" --type yaml --type md

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 70


๐Ÿ Script executed:

# ์›Œํฌํ”Œ๋กœ์šฐ ํŒŒ์ผ์—์„œ GITHUB_TOKEN ์‚ฌ์šฉ ํŒจํ„ด ํ™•์ธ
rg "GITHUB_TOKEN|secrets\." .github/workflows/ -A 2 -B 2

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 341


๐ŸŒ Web query:

Codium-ai pr-agent GitHub GITHUB_TOKEN environment variable requirements documentation

๐Ÿ’ก Result:

Summary โ€” GITHUB_TOKEN for Codiumโ€‘AI / pr-agent (GitHub Action)

  • pr-agent expects a GitHub token (you can use the automatically-created secrets.GITHUB_TOKEN) to call the GitHub API. [1][2]
  • Recommended job permissions in workflows depend on the features you enable; typical minimal permissions are:
    • contents: read (to read repo files) and
    • pull-requests: write (to post reviews/comments) โ€” or issues: write for issue-based workflows. Set these with the workflow permissions key. [2][1][3]
  • If your workflow needs actions that GITHUB_TOKEN cannot grant (or your organization restricts GITHUB_TOKEN write access), use a PAT or a GitHub App installation token with the appropriate repo scopes. [3][1]

Sources

  • pr-agent GitHub / Qodo Merge docs (Run as GitHub Action). [1]
  • pr-agent "Help Docs" example workflow showing env and permissions (contents read, issues/pull-requests write). [2]
  • GitHub Actions docs on GITHUB_TOKEN and configuring permissions (how to change scopes, when to use PAT/GitHub App). [3]

๐Ÿ Script executed:

# ์›Œํฌํ”Œ๋กœ์šฐ ํŒŒ์ผ์˜ ์ „์ฒด ๊ตฌ์กฐ ํ™•์ธ (์ˆจ๊ฒจ์ง„ ๋ถ€๋ถ„์ด ์žˆ๋Š”์ง€ ํ™•์ธ)
wc -l .github/workflows/main.yml

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 117


secrets.GITHUB_TOKEN ์‚ฌ์šฉ์œผ๋กœ ๋ณ€๊ฒฝํ•˜์„ธ์š”.

Codium-ai/pr-agent๋Š” ์ž๋™์œผ๋กœ ์ œ๊ณต๋˜๋Š” secrets.GITHUB_TOKEN์„ ์‚ฌ์šฉํ•˜๋„๋ก ๋ฌธ์„œํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ปค์Šคํ…€ ์‹œํฌ๋ฆฟ G_TOKEN์€ ๋ถˆํ•„์š”ํ•˜๋ฉฐ, ํ‘œ์ค€ GitHub ํ† ํฐ์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค:

๊ถŒ์žฅ ์ˆ˜์ •
         env:
           OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
-          GITHUB_TOKEN: ${{ secrets.G_TOKEN }}
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
๐Ÿ“ 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
GITHUB_TOKEN: ${{ secrets.G_TOKEN }}
env:
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
๐Ÿค– Prompt for AI Agents
In .github/workflows/main.yml around line 13 the workflow sets GITHUB_TOKEN from
the custom secret secrets.G_TOKEN; replace that reference with the built-in
secrets.GITHUB_TOKEN so the workflow uses the automatic GitHub token, and
update/remove any other occurrences of G_TOKEN in the workflow file to avoid
references to the custom secret.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.loopers.domain.like.LikeService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
* packageName : com.loopers.application.like
Expand All @@ -18,7 +17,6 @@
*/
@Component
@RequiredArgsConstructor
@Transactional
public class LikeFacade {

private final LikeService likeService;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.loopers.application.ranking;

public record ProductRankSnapshot(
long rank,
Long productId,
double score
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,30 @@ public class RankingFacade {
private final RankingService rankingService;
private final ProductService productService;
private final BrandService brandService;
private final RankingMaterializedViewService rankingMaterializedViewService;

@Transactional(readOnly = true)
public List<RankingInfo> getRankingItems(String date, int page, int size) {
return rankingService.getRankingRows(date, page, size)
public List<RankingInfo> getRankingItems(String date, RankingPeriod period, int page, int size) {
if (period.isDaily()) {
return rankingService.getRankingRows(date, page, size)
.stream()
.map(this::toDto)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
return rankingMaterializedViewService.getRankings(period, period.resolveKey(date), page, size)
.stream()
.map(this::toDto)
.map(this::toSnapshotDto)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

@Transactional(readOnly = true)
public long count(String date) {
return rankingService.count(date);
public long count(String date, RankingPeriod period) {
if (period.isDaily()) {
return rankingService.count(date);
}
return rankingMaterializedViewService.count(period, period.resolveKey(date));
}

@Transactional(readOnly = true)
Expand All @@ -53,8 +64,19 @@ private RankingInfo toDto(RankingRow row) {
return null;
}

Product product = productService.getProduct(row.productId());
return createRankingInfo(row.productId(), row.rank(), row.score());
}

private RankingInfo toSnapshotDto(ProductRankSnapshot snapshot) {
if (snapshot.productId() == null) {
return null;
}
return createRankingInfo(snapshot.productId(), snapshot.rank(), snapshot.score());
}

private RankingInfo createRankingInfo(Long productId, long rank, double score) {
Product product = productService.getProduct(productId);
ProductInfo productInfo = ProductInfo.of(product, brandService.getBrand(product.getBrandId()));
return new RankingInfo(row.rank(), row.score(), productInfo);
return new RankingInfo(rank, score, productInfo);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.loopers.application.ranking;

import com.loopers.domain.ranking.MvProductRankMonthly;
import com.loopers.domain.ranking.MvProductRankWeekly;
import com.loopers.infrastructure.ranking.MvProductRankMonthlyJpaRepository;
import com.loopers.infrastructure.ranking.MvProductRankWeeklyJpaRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class RankingMaterializedViewService {

private final MvProductRankWeeklyJpaRepository weeklyRepository;
private final MvProductRankMonthlyJpaRepository monthlyRepository;

public List<ProductRankSnapshot> getRankings(RankingPeriod period, String periodKey, int page, int size) {
List<ProductRankSnapshot> snapshots = fetch(period, periodKey);
int safeSize = Math.max(size, 1);
long skip = (long) Math.max(page, 0) * safeSize;
return snapshots.stream()
.skip(skip)
.limit(safeSize)
.toList();
}
Comment on lines +18 to +26
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

๋ฉ”๋ชจ๋ฆฌ ๋‚ด ํŽ˜์ด์ง€๋„ค์ด์…˜์€ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ์œ ๋ฐœํ•ฉ๋‹ˆ๋‹ค.

fetch() ๋ฉ”์„œ๋“œ๊ฐ€ ํ•ด๋‹น ๊ธฐ๊ฐ„์˜ ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์˜จ ํ›„ stream().skip().limit()์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋žญํ‚น ๋ฐ์ดํ„ฐ๊ฐ€ ์ˆ˜์ฒœ ๊ฐœ ์ด์ƒ์ผ ๊ฒฝ์šฐ ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๋กœ๋“œํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ์™€ DB ๋ถ€ํ•˜๊ฐ€ ์ฆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

๋ฆฌํฌ์ง€ํ† ๋ฆฌ ๋ ˆ๋ฒจ์—์„œ Pageable์„ ์‚ฌ์šฉํ•œ DB ํŽ˜์ด์ง€๋„ค์ด์…˜์œผ๋กœ ๋ณ€๊ฒฝํ•˜์„ธ์š”.

๐Ÿ”Ž ์ œ์•ˆ: ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์—์„œ ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ฒ˜๋ฆฌ

๋ฆฌํฌ์ง€ํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •:

// MvProductRankWeeklyJpaRepository
Page<MvProductRankWeekly> findByIdPeriodKey(String periodKey, Pageable pageable);

๊ทธ๋ฆฌ๊ณ  ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •:

     public List<ProductRankSnapshot> getRankings(RankingPeriod period, String periodKey, int page, int size) {
-        List<ProductRankSnapshot> snapshots = fetch(period, periodKey);
         int safeSize = Math.max(size, 1);
-        long skip = (long) Math.max(page, 0) * safeSize;
-        return snapshots.stream()
-                .skip(skip)
-                .limit(safeSize)
-                .toList();
+        int safePage = Math.max(page, 0);
+        Pageable pageable = PageRequest.of(safePage, safeSize, Sort.by("rank").ascending());
+        return fetchPaged(period, periodKey, pageable);
     }

Committable suggestion skipped: line range outside the PR's diff.

๐Ÿค– Prompt for AI Agents
In
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingMaterializedViewService.java
around lines 18 to 26, the current implementation fetches all snapshots into
memory and uses stream().skip().limit() for pagination which causes high memory
and DB load; change to DB-level pagination by adding a repository method that
accepts a Pageable (e.g., findByIdPeriodKey(String periodKey, Pageable
pageable)) and modify this service method to build a PageRequest from the
incoming page and size (clamp page >= 0 and size >= 1), call the repository to
retrieve a Page<MvProductRankWeekly>, map the page content to
List<ProductRankSnapshot>, and return that list (this avoids loading all records
into memory and delegates pagination to the database).


public long count(RankingPeriod period, String periodKey) {
return fetch(period, periodKey).size();
}
Comment on lines +28 to +30
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

count ๊ตฌํ˜„์ด ๋น„ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.

๋ชจ๋“  ๋ ˆ์ฝ”๋“œ๋ฅผ fetch()๋กœ ๊ฐ€์ ธ์˜จ ํ›„ size()๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ๋น„ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค. ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— COUNT ์ฟผ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ DB ๋ ˆ๋ฒจ์—์„œ ์ง‘๊ณ„ํ•˜์„ธ์š”.

๐Ÿ”Ž ์ œ์•ˆ: COUNT ์ฟผ๋ฆฌ ์ถ”๊ฐ€

๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ๋‹ค์Œ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€:

// MvProductRankWeeklyJpaRepository
long countByIdPeriodKey(String periodKey);

๊ทธ๋ฆฌ๊ณ  ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •:

     public long count(RankingPeriod period, String periodKey) {
-        return fetch(period, periodKey).size();
+        if (period == RankingPeriod.WEEKLY) {
+            return weeklyRepository.countByIdPeriodKey(periodKey);
+        }
+        if (period == RankingPeriod.MONTHLY) {
+            return monthlyRepository.countByIdPeriodKey(periodKey);
+        }
+        throw new IllegalArgumentException("Unsupported period for MV: " + period);
     }
๐Ÿค– Prompt for AI Agents
In
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingMaterializedViewService.java
around lines 28-30, the current count implementation fetches all records then
calls size(), which is inefficient; add a DB-level count method to the
appropriate JPA repository (e.g., long countByIdPeriodKey(String periodKey) or a
signature including period if needed), then change this service method to call
that repository count method and return its result instead of fetch(). Ensure
the repository method name matches your entity's ID mapping and that the service
injects/uses the repository call directly to avoid loading full entities into
memory.


private List<ProductRankSnapshot> fetch(RankingPeriod period, String periodKey) {
if (period == RankingPeriod.WEEKLY) {
return weeklyRepository.findByIdPeriodKeyOrderByRankAsc(periodKey).stream()
.map(this::fromWeekly)
.toList();
}
if (period == RankingPeriod.MONTHLY) {
return monthlyRepository.findByIdPeriodKeyOrderByRankAsc(periodKey).stream()
.map(this::fromMonthly)
.toList();
}
throw new IllegalArgumentException("Unsupported period for MV: " + period);
}

private ProductRankSnapshot fromWeekly(MvProductRankWeekly entity) {
return new ProductRankSnapshot(entity.getRank(), entity.getProductId(), entity.getScore());
}

private ProductRankSnapshot fromMonthly(MvProductRankMonthly entity) {
return new ProductRankSnapshot(entity.getRank(), entity.getProductId(), entity.getScore());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.loopers.application.ranking;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.WeekFields;
import java.util.Arrays;
import java.util.Locale;

public enum RankingPeriod {
DAILY("daily") {
@Override
public LocalDate resolveStartDate(String date) {
return parse(date);
}

@Override
public String resolveKey(String date) {
return parse(date).format(FORMATTER);
}
},
WEEKLY("weekly") {
@Override
public LocalDate resolveStartDate(String date) {
LocalDate target = parse(date);
return target.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
}

@Override
public String resolveKey(String date) {
return toYearMonthWeek(resolveStartDate(date));
}
},
MONTHLY("monthly") {
@Override
public LocalDate resolveStartDate(String date) {
LocalDate target = parse(date);
return target.withDayOfMonth(1);
}

@Override
public String resolveKey(String date) {
return toYearMonth(resolveStartDate(date));
}
};

private static final ZoneId ZONE_ID = ZoneId.of("Asia/Seoul");
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.BASIC_ISO_DATE;

private final String value;

RankingPeriod(String value) {
this.value = value;
}

public static RankingPeriod from(String value) {
if (value == null) {
return DAILY;
}
return Arrays.stream(values())
.filter(period -> period.value.equalsIgnoreCase(value))
.findFirst()
.orElse(DAILY);
}

public abstract LocalDate resolveStartDate(String date);

public abstract String resolveKey(String date);

public boolean isDaily() {
return this == DAILY;
}

private static LocalDate parse(String date) {
if (date == null || date.isBlank()) {
return LocalDate.now(ZONE_ID);
}
return LocalDate.parse(date, FORMATTER);
}

private static String toYearMonthWeek(LocalDate target) {
WeekFields weekFields = WeekFields.of(Locale.KOREA);
int weekBasedYear = target.get(weekFields.weekBasedYear());
int week = target.get(weekFields.weekOfWeekBasedYear());
return String.format("%04d-W%02d", weekBasedYear, week);
}
Comment on lines +83 to +88
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸก Minor

Locale.KOREA์™€ ์›”์š”์ผ ์‹œ์ž‘ ๊ฐ„ ๋ถˆ์ผ์น˜ (RankingPeriodResolver์™€ ๋™์ผ ์ด์Šˆ)

WEEKLY.resolveStartDate()๋Š” ์›”์š”์ผ์„ ๊ธฐ์ค€์œผ๋กœ ํ•˜์ง€๋งŒ, toYearMonthWeek()๋Š” Locale.KOREA์˜ WeekFields๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. RankingPeriodResolver์—์„œ ์–ธ๊ธ‰ํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•œ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค. ์ผ๊ด€์„ฑ์„ ์œ„ํ•ด WeekFields.ISO ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ์ˆ˜์ • ์ œ์•ˆ
     private static String toYearMonthWeek(LocalDate target) {
-        WeekFields weekFields = WeekFields.of(Locale.KOREA);
+        WeekFields weekFields = WeekFields.ISO;
         int weekBasedYear = target.get(weekFields.weekBasedYear());
         int week = target.get(weekFields.weekOfWeekBasedYear());
         return String.format("%04d-W%02d", weekBasedYear, week);
     }
๐Ÿ“ 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
private static String toYearMonthWeek(LocalDate target) {
WeekFields weekFields = WeekFields.of(Locale.KOREA);
int weekBasedYear = target.get(weekFields.weekBasedYear());
int week = target.get(weekFields.weekOfWeekBasedYear());
return String.format("%04d-W%02d", weekBasedYear, week);
}
private static String toYearMonthWeek(LocalDate target) {
WeekFields weekFields = WeekFields.ISO;
int weekBasedYear = target.get(weekFields.weekBasedYear());
int week = target.get(weekFields.weekOfWeekBasedYear());
return String.format("%04d-W%02d", weekBasedYear, week);
}
๐Ÿค– Prompt for AI Agents
In
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingPeriod.java
around lines 83 to 88, toYearMonthWeek(...) uses WeekFields.of(Locale.KOREA)
which conflicts with WEEKLY.resolveStartDate()'s Monday-based logic; change it
to use WeekFields.ISO so week-based-year and week-of-week-based-year are
computed consistently (i.e., replace WeekFields.of(Locale.KOREA) with
WeekFields.ISO and ensure the returned format remains "%04d-W%02d").


private static String toYearMonth(LocalDate target) {
return String.format("%04d-%02d", target.getYear(), target.getMonthValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ public class BrandService {

private final BrandRepository brandRepository;

@Transactional
public void save(Brand brand) {
brandRepository.save(brand);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public static Order create(String userId) {
}

public void addOrderItem(OrderItem orderItem) {
orderItem.setOrder(this);
this.orderItems.add(orderItem);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ public void charge(Long chargeAmount) {
throw new CoreException(ErrorType.BAD_REQUEST, "0์› ์ดํ•˜๋กœ ํฌ์ธํŠธ๋ฅผ ์ถฉ์ „ ํ• ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
}
this.balance += chargeAmount;
new Point(this.userId, this.balance);
}

public void use(Long useAmount) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ public Point usePoint(String userId, Long useAmount) {
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "ํฌ์ธํŠธ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."));

if (useAmount == null || useAmount <= 0) {
throw new CoreException(ErrorType.NOT_FOUND, "์ฐจ๊ฐํ•  ํฌ์ธํŠธ๋Š” 1 ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.");
throw new CoreException(ErrorType.BAD_REQUEST, "์ฐจ๊ฐํ•  ํฌ์ธํŠธ๋Š” 1 ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.");
}

if (point.getBalance() < useAmount) {
throw new CoreException(ErrorType.NOT_FOUND, "ํฌ์ธํŠธ๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค.");
throw new CoreException(ErrorType.BAD_REQUEST, "ํฌ์ธํŠธ๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค.");
}

point.use(useAmount);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,17 @@ private Long requireValidPrice(Long price) {
return price;
}

public Long requireValidLikeCount(Long likeCount) {
private Long requireValidLikeCount(Long likeCount) {
if (likeCount == null || likeCount < 0) {
throw new CoreException(ErrorType.BAD_REQUEST, "์ข‹์•„์š” ๊ฐœ์ˆ˜๋Š” 0๊ฐœ ๋ฏธ๋งŒ์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
}
return likeCount;
}

private Long requireValidStock(Long stock) {
if (stock == null) {
throw new CoreException(ErrorType.BAD_REQUEST, "์ƒํ’ˆ ์žฌ๊ณ ๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.");
}
if (stock < 0) {
throw new CoreException(ErrorType.BAD_REQUEST, "์ƒํ’ˆ ์žฌ๊ณ ๋Š” 0 ๋ฏธ๋งŒ์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.loopers.support.error.ErrorType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
* packageName : com.loopers.domain.product
Expand All @@ -27,6 +28,7 @@ public class ProductDomainService {
private final BrandRepository brandRepository;
private final LikeRepository likeRepository;

@Transactional(readOnly = true)
public ProductDetail getProductDetail(Long id) {
Product product = productRepository.findById(id)
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "์ƒํ’ˆ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.loopers.domain.ranking;

import jakarta.persistence.Column;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@Table(name = "mv_product_rank_monthly")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MvProductRankMonthly {

@EmbeddedId
private ProductRankId id;

@Column(name = "like_count", nullable = false)
private long likeCount;

@Column(name = "sales_count", nullable = false)
private long salesCount;

@Column(name = "score", nullable = false)
private double score;

@Column(name = "rank", nullable = false)
private int rank;

@Column(name = "aggregated_at", nullable = false)
private LocalDateTime aggregatedAt;

private MvProductRankMonthly(
ProductRankId id,
long likeCount,
long salesCount,
double score,
int rank,
LocalDateTime aggregatedAt
) {
this.id = id;
this.likeCount = likeCount;
this.salesCount = salesCount;
this.score = score;
this.rank = rank;
this.aggregatedAt = aggregatedAt;
}

public static MvProductRankMonthly create(
String yearMonth,
Long productId,
long likeCount,
long salesCount,
double score,
int rank,
LocalDateTime aggregatedAt
) {
return new MvProductRankMonthly(
ProductRankId.of(yearMonth, productId),
likeCount,
salesCount,
score,
rank,
aggregatedAt
);
}

public String getPeriodKey() {
return id != null ? id.getPeriodKey() : null;
}

public Long getProductId() {
return id != null ? id.getProductId() : null;
}
}
Loading