Skip to content

Feat: 그룹 내 카드셋 전체 조회#6

Merged
stoneTiger0912 merged 5 commits intomainfrom
feat/add-group-in-cardset
Mar 16, 2026
Merged

Feat: 그룹 내 카드셋 전체 조회#6
stoneTiger0912 merged 5 commits intomainfrom
feat/add-group-in-cardset

Conversation

@stoneTiger0912
Copy link
Member

@stoneTiger0912 stoneTiger0912 commented Mar 16, 2026

📝 변경 내용


✅ 체크리스트

  • 코드가 정상적으로 동작함
  • 테스트 코드 통과함
  • 문서(README 등)를 최신화함
  • 코드 스타일 가이드 준수

💬 기타 참고 사항

Summary by CodeRabbit

  • 새로운 기능

    • 카드셋에 관리자 목록 노출 및 관리자 지정 기능 추가
    • 그룹별 카드셋 조회용 신규 엔드포인트 추가
    • 카드셋 목록/상세에 좋아요·북마크 상태 및 관리자 정보 포함
  • 개선 사항

    • 카드 생성 시 기본 카드 수를 10개로 정규화
    • 카드셋 생성·수정 시 관리자 지정 및 갱신 지원, 목록/상세 응답 개선(일괄 조회 최적화 포함)
  • 제거

    • 카드 수량 업데이트(카드카운트) 엔드포인트 비활성화

@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3f8ce44e-7f92-49fa-977b-e96e4777f734

📥 Commits

Reviewing files that changed from the base of the PR and between 5d327b7 and fc2e473.

📒 Files selected for processing (6)
  • src/cardset/application/cardset.use-case.ts
  • src/cardset/infrastructure/grpc/group-grpc.client.ts
  • src/cardset/infrastructure/grpc/image-grpc.client.ts
  • src/cardset/infrastructure/grpc/reaction-grpc.client.ts
  • src/cardset/infrastructure/grpc/user-grpc.client.ts
  • src/shared/grpc/grpc-client.module.ts

Walkthrough

관리자 할당 기능과 좋아요·북마크 메타데이터 조회를 위해 Reaction/User gRPC 클라이언트가 통합되고, 생성·수정 시 관리자 검증·저장 로직이 추가되며 조회 응답에 liked/bookmarked/managers 정보가 포함되도록 확장됩니다. (50단어 이내)

Changes

Cohort / File(s) Summary
핵심 유스케이스
src/cardset/application/cardset.use-case.ts
ReactionGrpcClient, UserGrpcClient 의존성 추가. create/update에 managerIds 검증 및 다수 CardsetManager 영속화. findAll/findOne/findByGroupId에서 liked/bookmarked/managers 포함 응답으로 확장. 배치 조회 헬퍼(getManagersForCardSets) 및 skipUserGrpc/skipReactionGrpc 플래그 도입.
요청/응답 DTO
src/cardset/application/dto/request/create-cardset.request.ts, src/cardset/application/dto/request/update-cardset.request.ts, src/cardset/application/dto/response/cardset.response.ts, src/cardset/application/dto/response/cardset-list-item.response.ts, src/cardset/application/dto/response/manager-info.response.ts
CreateCardsetRequest에서 cardCount 제거, managerIds?: number[] 추가. UpdateCardsetRequest에 managerIds 추가. CardsetResponse/CardsetListItemResponse에 liked, bookmarked, managers 필드 추가. ManagerInfoResponse 신규 도입.
gRPC 클라이언트 및 프로토
src/cardset/infrastructure/grpc/reaction-grpc.client.ts, src/cardset/infrastructure/grpc/user-grpc.client.ts, src/proto/reaction.proto, src/shared/grpc/grpc-client.module.ts
ReactionGrpcClient(isLiked/isBookmarked/areLiked/areBookmarked) 추가. UserGrpcClient(getUsersByIds) 추가. reaction.proto 추가 및 GrpcClientModule에 REACTION_GRPC_CLIENT 등록 및 로더 설정 변경.
리포지토리 인터페이스/구현
src/cardset/domain/repository/cardset-manager.repository.ts, src/cardset/domain/repository/cardset.repository.ts, src/cardset/infrastructure/persistence/cardset-manager.repository.impl.ts, src/cardset/infrastructure/persistence/cardset.repository.impl.ts
ICardsetManagerRepository에 findByCardSetIds 추가 및 구현. ICardsetRepository에 페이징 옵션/결과 타입 추가 및 findAllPaged, findByGroupId 구현 추가(페이징/필터/정렬 포함).
HTTP 컨트롤러 및 모듈
src/cardset/infrastructure/http/group-cardset.controller.ts, src/cardset/infrastructure/http/cardset.controller.ts, src/cardset/infrastructure/http/card.controller.ts, src/cardset/cardset.module.ts
GroupCardsetController(GET /groups/:groupId/card-sets) 신규 추가. CardsetController에 ApiExtraModels 등록 및 응답 매핑 확대(관리자/반응 포함). updateCardCount 엔드포인트와 CardController의 라우트들이 주석 처리되어 비활성화. CardsetModule에 ReactionGrpcClient, UserGrpcClient, GroupCardsetController 등록.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant GroupCardsetController
    participant CardsetUseCase
    participant CardsetRepository
    participant CardsetManagerRepository
    participant ReactionGrpcClient
    participant UserGrpcClient

    Client->>GroupCardsetController: GET /groups/:groupId/card-sets (X-USER-ID)
    GroupCardsetController->>CardsetUseCase: findByGroupId(groupId, userId)
    CardsetUseCase->>CardsetRepository: findByGroupId(groupId)
    CardsetRepository-->>CardsetUseCase: Cardset[]
    CardsetUseCase->>CardsetManagerRepository: findByCardSetIds(cardsetIds)
    CardsetManagerRepository-->>CardsetUseCase: CardsetManager[]
    CardsetUseCase->>UserGrpcClient: getUsersByIds(managerIds)
    UserGrpcClient-->>CardsetUseCase: UserInfo[]
    CardsetUseCase->>ReactionGrpcClient: areLiked(cardsetIds, userId) / areBookmarked(cardsetIds, userId)
    ReactionGrpcClient-->>CardsetUseCase: Map<id,liked> / Map<id,bookmarked>
    CardsetUseCase-->>GroupCardsetController: enriched CardsetListItemResponse[]
    GroupCardsetController-->>Client: ApiResponse<CardsetListItemResponse[]>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 내가 말하네, 관리자 모여라 춤추자,
좋아요·북마크 손에 손잡고, gRPC 친구들 안아주네.
그룹별로 돌며 카드셋 빛나니,
당근 들고 축하하러 달려갈래요! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 변경 사항의 주요 기능을 명확하게 설명합니다. 그룹 내 카드셋 조회 기능 추가라는 핵심 변경사항을 간결하게 표현했습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-group-in-cardset
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/cardset/infrastructure/http/card.controller.ts (1)

1-23: ⚠️ Potential issue | 🟠 Major

주석 처리 상태와 불일치한 import/DI로 CI가 깨집니다.

Line 1-23, 28의 심볼들이 현재 활성 코드에서 사용되지 않아 ESLint 실패(Get is defined but never used, Line 3)가 발생했습니다. 임시 비활성화 상태를 유지할 거면 import와 constructor를 같이 정리해 CI를 통과시켜야 합니다.

임시 비활성화 상태 기준 최소 정리안
 import {
-  Controller,
-  Get,
-  Post,
-  Put,
-  Delete,
-  Body,
-  Param,
-  Headers,
+  Controller,
 } from '@nestjs/common';
 import {
-  ApiTags,
-  ApiOperation,
-  ApiResponse as SwaggerApiResponse,
-  ApiParam,
+  ApiTags,
 } from '@nestjs/swagger';
-import { CardUseCase } from '../../application/card.use-case';
-import { CreateCardRequest } from '../../application/dto/request/create-card.request';
-import { UpdateCardRequest } from '../../application/dto/request/update-card.request';
-import { ReorderCardsRequest } from '../../application/dto/request/reorder-cards.request';
-import { CardCreateResponse } from '../../application/dto/response/card-create.response';
-import { CardResponse } from '../../application/dto/response/card.response';
-import { ApiResponse } from '../../../shared/common/api-response';

 `@ApiTags`('cards')
 `@Controller`('cards')
 export class CardController {
-  constructor(private readonly cardUseCase: CardUseCase) { }
+  constructor() {}

Also applies to: 28-28

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cardset/infrastructure/http/card.controller.ts` around lines 1 - 23, The
file has unused Nest/Swagger imports (Get, Post, Put, Delete, Body, Param,
Headers, ApiOperation, ApiResponse/SwaggerApiResponse, ApiParam) and an unused
injected dependency CardUseCase (constructor) causing ESLint errors; either
restore the commented controller methods that reference these symbols or remove
the unused imports and the CardUseCase constructor parameter so the import/DI
surface matches active code—specifically update imports at the top (remove the
unused symbols) and update the class constructor that injects CardUseCase
(remove the injection if no methods use it) to eliminate the "defined but never
used" diagnostics.
src/cardset/infrastructure/http/cardset.controller.ts (1)

134-134: ⚠️ Potential issue | 🟡 Minor

update 메서드의 응답이 기본값을 반환합니다.

update 메서드는 CardsetResponse.from(cardset)만 호출하여 liked, bookmarked, managers 필드가 항상 기본값(false, false, [])으로 설정됩니다. 반면 findAllfindOne은 실제 값을 전달하고 있습니다. 응답 일관성을 위해 update 메서드도 동일하게 추가 매개변수를 전달해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cardset/infrastructure/http/cardset.controller.ts` at line 134, The
update method currently returns
ApiResponse.success(CardsetResponse.from(cardset)) which causes liked,
bookmarked and managers to remain defaulted; update the return in the update
handler to call CardsetResponse.from with the same extra arguments used by
findAll/findOne (pass the cardset's liked, bookmarked and managers values or the
same parameter signature those calls use) so the response includes actual
liked/bookmarked booleans and managers array and matches the other endpoints'
shape.
🧹 Nitpick comments (10)
src/cardset/infrastructure/http/cardset.controller.ts (1)

150-170: 주석 처리된 코드를 제거해 주세요.

더 이상 사용하지 않는 updateCardCount 엔드포인트가 주석 처리되어 있습니다. 코드베이스를 깔끔하게 유지하기 위해 주석 처리된 코드는 제거하는 것이 좋습니다. 필요 시 Git 히스토리에서 복구할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cardset/infrastructure/http/cardset.controller.ts` around lines 150 -
170, Remove the entire commented-out updateCardCount endpoint block (the
commented async function named updateCardCount and its related decorators and
Swagger comments) from the cardset controller file; ensure no references remain
to updateCardCount in the class and verify imports/unused symbols are adjusted
if necessary so the controller compiles cleanly after deletion.
src/cardset/application/dto/response/cardset-list-item.response.ts (2)

64-71: Manager 매핑 로직이 중복됩니다.

이 매핑 로직이 CardsetResponse.from (lines 79-86)에도 동일하게 존재합니다. ManagerInfoResponse에 정적 팩토리 메서드를 추가하여 중복을 제거하는 것을 고려해 주세요.

♻️ 제안하는 리팩토링

manager-info.response.ts에 팩토리 메서드 추가:

static from(userInfo: UserInfo): ManagerInfoResponse {
  const info = new ManagerInfoResponse();
  info.id = userInfo.id;
  info.email = userInfo.email;
  info.nickname = userInfo.nickname;
  info.profileImageUrl = userInfo.profileImageUrl;
  return info;
}

그 후 사용처에서:

-res.managers = managers.map((m) => {
-  const info = new ManagerInfoResponse();
-  info.id = m.id;
-  info.email = m.email;
-  info.nickname = m.nickname;
-  info.profileImageUrl = m.profileImageUrl;
-  return info;
-});
+res.managers = managers.map(ManagerInfoResponse.from);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cardset/application/dto/response/cardset-list-item.response.ts` around
lines 64 - 71, Add a static factory method on ManagerInfoResponse (e.g., static
from(userInfo: UserInfo): ManagerInfoResponse) that constructs and returns a
ManagerInfoResponse from a UserInfo, then replace the duplicated mapping code in
both the res.managers assignment and inside CardsetResponse.from to call
ManagerInfoResponse.from(m) instead of manually copying fields; this removes the
duplication and centralizes the mapping logic.

4-4: Application 레이어에서 Infrastructure 레이어 타입을 직접 참조합니다.

UserInfo 타입을 Infrastructure 레이어(grpc/user-grpc.client.ts)에서 직접 import하고 있습니다. Clean Architecture 원칙에 따르면 Application 레이어는 Infrastructure 레이어에 의존하지 않아야 합니다.

UserInfo 인터페이스를 Application 또는 Domain 레이어로 이동하는 것을 고려해 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cardset/application/dto/response/cardset-list-item.response.ts` at line
4, The file cardset-list-item.response.ts imports the Infrastructure type
UserInfo directly, violating layer dependencies; create a new UserInfo interface
(or shared DTO) inside the Application or Domain layer (e.g., in application/dto
or domain/model) that matches the needed shape, update
cardset-list-item.response.ts to import that application/domain UserInfo instead
of ../../../infrastructure/grpc/user-grpc.client, and remove the infrastructure
import so the Application layer no longer depends on Infrastructure; ensure any
other files using the original Infrastructure UserInfo are updated to use the
new Application/Domain type or map between types where necessary.
src/cardset/application/dto/response/cardset.response.ts (1)

4-5: Application 레이어에서 Infrastructure 레이어 타입을 참조합니다.

CardsetListItemResponse와 동일한 이슈입니다. UserInfo 타입이 Infrastructure 레이어에서 import되고 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cardset/application/dto/response/cardset.response.ts` around lines 4 - 5,
Cardset.application is importing the infrastructure type UserInfo directly;
create or use an application-layer DTO instead and replace the infra import.
Define a new application DTO (e.g., UserInfoResponse or move an existing
ManagerInfoResponse usage) in the application/dto folder, update
cardset.response.ts to import that application-level type and ensure any
references in CardsetListItemResponse and ManagerInfoResponse use the new DTO so
the application layer no longer depends on infrastructure.
src/proto/reaction.proto (1)

1-2: Buf 린트 경고: 패키지 디렉토리 불일치.

Static analysis에서 보고된 대로, reaction 패키지의 파일은 reaction/ 디렉토리에 위치해야 하지만 현재 src/proto/에 있습니다. 프로젝트의 proto 파일 구성 규칙에 따라 이를 무시하거나, 디렉토리 구조를 조정해 주세요.

기존 user.proto도 같은 디렉토리에 있다면 일관성 있게 유지하는 것이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/proto/reaction.proto` around lines 1 - 2, The proto package/location
mismatch: reaction.proto declares package "reaction" but lives under src/proto/,
causing buf lint warnings; fix by moving reaction.proto into a matching
directory (e.g., create/update reaction/ and place reaction.proto there) or
alternatively change the declared package in reaction.proto to match the current
directory layout; reference the file name reaction.proto and the package
declaration "package reaction" (and ensure any related files like user.proto
follow the same convention) so imports and buf config remain consistent.
src/cardset/infrastructure/grpc/reaction-grpc.client.ts (1)

63-75: 빈 배열에 대한 조기 반환 최적화가 없습니다.

UserGrpcClient.getUsersByIds는 빈 배열 입력 시 조기 반환하지만, areLikedareBookmarked는 빈 cardSetIds 배열에 대해서도 불필요한 gRPC 호출을 수행합니다.

⚡ 최적화 제안
 async areLiked(
   cardSetIds: number[],
   userId: number,
 ): Promise<Map<number, boolean>> {
+  if (cardSetIds.length === 0) return new Map();
   const result = await firstValueFrom(
     this.reactionService.areLiked({

areBookmarked에도 동일하게 적용하세요.

Also applies to: 77-89

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cardset/infrastructure/grpc/reaction-grpc.client.ts` around lines 63 -
75, The methods areLiked and areBookmarked should avoid making a gRPC call when
cardSetIds is empty; add an early return that returns an empty Map<number,
boolean> immediately if the input array has length 0, instead of calling
reactionService.areLiked/areBookmarked and converting result.results; update the
implementations for areLiked and areBookmarked to check cardSetIds.length === 0
and return new Map() early.
src/cardset/application/cardset.use-case.ts (4)

144-148: N+1 gRPC 호출 문제: 가시성 검증

findAll에서 각 카드셋마다 isUserInGroup gRPC 호출이 발생합니다. 카드셋 수가 많아지면 심각한 성능 문제가 발생할 수 있습니다.

사용자가 속한 그룹 ID 목록을 한 번에 가져온 후 메모리에서 필터링하는 방식을 고려해 보세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cardset/application/cardset.use-case.ts` around lines 144 - 148, The loop
in findAll causes an N+1 gRPC call by invoking groupGrpcClient.isUserInGroup per
cardset; instead, fetch the user's group IDs once (e.g., call
groupGrpcClient.getUserGroupIds(userId) or similar) and then replace the
per-cardset check with an in-memory membership test: keep the Visibility.PUBLIC
check and for non-public use a set/Map lookup against the returned group IDs
when filtering cardsets into visibleCardsets; update references to
isUserInGroup, findAll, cardsets, visibleCardsets, Visibility and userId
accordingly.

173-176: N+1 gRPC 호출 문제: 이미지 URL 조회

루프 내에서 각 카드셋마다 getImageUrl gRPC 호출이 발생합니다. findByGroupId (line 277-280)에서도 동일한 패턴이 있습니다.

ImageGrpcClient에 배치 조회 메서드가 있다면 이를 활용하여 최적화할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cardset/application/cardset.use-case.ts` around lines 173 - 176, The loop
over visibleCardsets is causing N+1 gRPC calls by calling
imageGrpcClient.getImageUrl per cardset; instead collect the ids (or
imageRefIds) for cardsets that need images, call a batch method on
ImageGrpcClient (e.g., getImageUrls or add one if missing) to retrieve all URLs
in one request, then map the returned URLs back to each cardset (falling back to
defaultImageUrl) before returning; apply the same batching pattern to the
similar code path in findByGroupId to eliminate the per-item gRPC calls.

63-65: N+1 gRPC 호출 문제: 관리자 유효성 검증

추가 관리자 ID마다 checkUserInGroup gRPC 호출이 순차적으로 발생합니다. 관리자 수가 많아지면 성능 저하가 발생할 수 있습니다.

그룹 서비스에 배치 멤버십 확인 API가 있다면 이를 활용하는 것이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cardset/application/cardset.use-case.ts` around lines 63 - 65, The
current sequential loop over additionalManagerIds calling
this.groupGrpcClient.checkUserInGroup causes N+1 gRPC calls; replace it with a
single batched membership check (e.g., call
this.groupGrpcClient.checkUsersInGroup(dto.groupId, additionalManagerIds) or the
group service's batch membership API) and validate returned results, or if no
batch API exists at the moment, execute the checks in parallel with Promise.all
over additionalManagerIds to avoid serial calls; update any error
handling/response logic in the surrounding method (the block using
additionalManagerIds and checkUserInGroup) to handle batch responses and
missing/invalid manager IDs accordingly.

320-331: 순차적 삭제/저장 작업 - 배치 최적화 가능

기존 관리자를 하나씩 삭제하고 새 관리자를 하나씩 저장합니다. 관리자 수가 많아지면 성능 저하가 발생할 수 있습니다.

리포지토리에 deleteByCardSetIdsaveAll 같은 배치 메서드를 추가하면 성능을 개선할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cardset/application/cardset.use-case.ts` around lines 320 - 331, The
current implementation in cardset.use-case.ts iterates over existing managers
(cardsetManagerRepository.findAllByCardSetId) deleting each via delete(m.id) and
then saves new managers one-by-one (CardsetManager.create +
cardsetManagerRepository.save), which is inefficient for large sets; introduce
and use batch repository methods (e.g.,
cardsetManagerRepository.deleteByCardSetId(cardSetId) to remove all existing
managers in one operation and
cardsetManagerRepository.saveAll(cardsetManagerArray) to persist new managers in
a single call), build the array of CardsetManager instances from newManagerIds
before calling saveAll, and update the use-case to call these new batch methods
instead of the per-item delete/save loop.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cardset/application/cardset.use-case.ts`:
- Around line 305-335: The call to the external service
imageGrpcClient.changeImage is currently inside this.dataSource.transaction
which can cause inconsistent state if the DB transaction rolls back; move the
external call out of the transaction by performing the DB update first (the call
that ultimately runs this.cardsetRepository.update inside the transaction) and
then invoking imageGrpcClient.changeImage after the transaction
resolves/commits, or, if you must call it before, implement a compensation step
(e.g., call a revert method on imageGrpcClient) when the transaction fails;
update the flow around this.dataSource.transaction, imageGrpcClient.changeImage,
and cardsetRepository.update (and add a compensation call if available) to
ensure external changes are only applied after successful DB commit or are
rolled back on failure.

In `@src/cardset/infrastructure/grpc/reaction-grpc.client.ts`:
- Around line 28-39: The ReactionGrpcClient can throw if reactionService is
accessed before onModuleInit runs; change the pattern to lazily initialize and
guard access by making reactionService optional and adding a helper that returns
this.reactionService ??=
this.client.getService<ReactionService>('ReactionService'), then call that
helper from every public method instead of referencing this.reactionService
directly (or alternatively initialize in the constructor). Update the
ReactionGrpcClient class, the onModuleInit method, and all usages of
reactionService to use the new getter/initializer to ensure safe access.

In `@src/cardset/infrastructure/grpc/user-grpc.client.ts`:
- Around line 19-29: The UserGrpcClient has a race where userService is set in
onModuleInit but methods like getUsersByIds may be called earlier; update the
class to guard against uninitialized userService by either lazily resolving the
service from client inside getUsersByIds (or a private getter) or by throwing a
clear error when userService is falsy; specifically reference UserGrpcClient,
onModuleInit, userService and getUsersByIds and implement one of: (a) if
(!this.userService) this.userService =
this.client.getService<UserQueryService>('UserQueryService') at method entry, or
(b) throw new Error('UserGrpcClient not initialized') when userService is
undefined.

In `@src/cardset/infrastructure/http/card.controller.ts`:
- Around line 30-117: The CardController class has all route handlers commented
out but remains registered in the module via CardController, causing a breaking
404; either restore the routes in card.controller.ts (uncomment and ensure
methods like create, findByCardsetId, findOne, reorderCards, update, remove are
implemented) or remove CardController from the module's controllers array (the
registration referencing CardController) so it's not exposed; update related
documentation/migration notes accordingly to reflect the removal or
reinstatement.

In `@src/cardset/infrastructure/http/group-cardset.controller.ts`:
- Around line 30-37: In findByGroupId (controller method using
`@Headers`('X-USER-ID') userId and `@Param`('groupId') groupId) validate the
parseInt results before calling cardsetUseCase.findByGroupId: either apply
NestJS ParseIntPipe on the route parameter and a custom pipe or DTO for the
header, or manually parse both values to integers, check isNaN(parsedUserId) ||
isNaN(parsedGroupId) and throw a BadRequestException with a clear message if
invalid, then pass the safe parsed integers to cardsetUseCase.findByGroupId.

---

Outside diff comments:
In `@src/cardset/infrastructure/http/card.controller.ts`:
- Around line 1-23: The file has unused Nest/Swagger imports (Get, Post, Put,
Delete, Body, Param, Headers, ApiOperation, ApiResponse/SwaggerApiResponse,
ApiParam) and an unused injected dependency CardUseCase (constructor) causing
ESLint errors; either restore the commented controller methods that reference
these symbols or remove the unused imports and the CardUseCase constructor
parameter so the import/DI surface matches active code—specifically update
imports at the top (remove the unused symbols) and update the class constructor
that injects CardUseCase (remove the injection if no methods use it) to
eliminate the "defined but never used" diagnostics.

In `@src/cardset/infrastructure/http/cardset.controller.ts`:
- Line 134: The update method currently returns
ApiResponse.success(CardsetResponse.from(cardset)) which causes liked,
bookmarked and managers to remain defaulted; update the return in the update
handler to call CardsetResponse.from with the same extra arguments used by
findAll/findOne (pass the cardset's liked, bookmarked and managers values or the
same parameter signature those calls use) so the response includes actual
liked/bookmarked booleans and managers array and matches the other endpoints'
shape.

---

Nitpick comments:
In `@src/cardset/application/cardset.use-case.ts`:
- Around line 144-148: The loop in findAll causes an N+1 gRPC call by invoking
groupGrpcClient.isUserInGroup per cardset; instead, fetch the user's group IDs
once (e.g., call groupGrpcClient.getUserGroupIds(userId) or similar) and then
replace the per-cardset check with an in-memory membership test: keep the
Visibility.PUBLIC check and for non-public use a set/Map lookup against the
returned group IDs when filtering cardsets into visibleCardsets; update
references to isUserInGroup, findAll, cardsets, visibleCardsets, Visibility and
userId accordingly.
- Around line 173-176: The loop over visibleCardsets is causing N+1 gRPC calls
by calling imageGrpcClient.getImageUrl per cardset; instead collect the ids (or
imageRefIds) for cardsets that need images, call a batch method on
ImageGrpcClient (e.g., getImageUrls or add one if missing) to retrieve all URLs
in one request, then map the returned URLs back to each cardset (falling back to
defaultImageUrl) before returning; apply the same batching pattern to the
similar code path in findByGroupId to eliminate the per-item gRPC calls.
- Around line 63-65: The current sequential loop over additionalManagerIds
calling this.groupGrpcClient.checkUserInGroup causes N+1 gRPC calls; replace it
with a single batched membership check (e.g., call
this.groupGrpcClient.checkUsersInGroup(dto.groupId, additionalManagerIds) or the
group service's batch membership API) and validate returned results, or if no
batch API exists at the moment, execute the checks in parallel with Promise.all
over additionalManagerIds to avoid serial calls; update any error
handling/response logic in the surrounding method (the block using
additionalManagerIds and checkUserInGroup) to handle batch responses and
missing/invalid manager IDs accordingly.
- Around line 320-331: The current implementation in cardset.use-case.ts
iterates over existing managers (cardsetManagerRepository.findAllByCardSetId)
deleting each via delete(m.id) and then saves new managers one-by-one
(CardsetManager.create + cardsetManagerRepository.save), which is inefficient
for large sets; introduce and use batch repository methods (e.g.,
cardsetManagerRepository.deleteByCardSetId(cardSetId) to remove all existing
managers in one operation and
cardsetManagerRepository.saveAll(cardsetManagerArray) to persist new managers in
a single call), build the array of CardsetManager instances from newManagerIds
before calling saveAll, and update the use-case to call these new batch methods
instead of the per-item delete/save loop.

In `@src/cardset/application/dto/response/cardset-list-item.response.ts`:
- Around line 64-71: Add a static factory method on ManagerInfoResponse (e.g.,
static from(userInfo: UserInfo): ManagerInfoResponse) that constructs and
returns a ManagerInfoResponse from a UserInfo, then replace the duplicated
mapping code in both the res.managers assignment and inside CardsetResponse.from
to call ManagerInfoResponse.from(m) instead of manually copying fields; this
removes the duplication and centralizes the mapping logic.
- Line 4: The file cardset-list-item.response.ts imports the Infrastructure type
UserInfo directly, violating layer dependencies; create a new UserInfo interface
(or shared DTO) inside the Application or Domain layer (e.g., in application/dto
or domain/model) that matches the needed shape, update
cardset-list-item.response.ts to import that application/domain UserInfo instead
of ../../../infrastructure/grpc/user-grpc.client, and remove the infrastructure
import so the Application layer no longer depends on Infrastructure; ensure any
other files using the original Infrastructure UserInfo are updated to use the
new Application/Domain type or map between types where necessary.

In `@src/cardset/application/dto/response/cardset.response.ts`:
- Around line 4-5: Cardset.application is importing the infrastructure type
UserInfo directly; create or use an application-layer DTO instead and replace
the infra import. Define a new application DTO (e.g., UserInfoResponse or move
an existing ManagerInfoResponse usage) in the application/dto folder, update
cardset.response.ts to import that application-level type and ensure any
references in CardsetListItemResponse and ManagerInfoResponse use the new DTO so
the application layer no longer depends on infrastructure.

In `@src/cardset/infrastructure/grpc/reaction-grpc.client.ts`:
- Around line 63-75: The methods areLiked and areBookmarked should avoid making
a gRPC call when cardSetIds is empty; add an early return that returns an empty
Map<number, boolean> immediately if the input array has length 0, instead of
calling reactionService.areLiked/areBookmarked and converting result.results;
update the implementations for areLiked and areBookmarked to check
cardSetIds.length === 0 and return new Map() early.

In `@src/cardset/infrastructure/http/cardset.controller.ts`:
- Around line 150-170: Remove the entire commented-out updateCardCount endpoint
block (the commented async function named updateCardCount and its related
decorators and Swagger comments) from the cardset controller file; ensure no
references remain to updateCardCount in the class and verify imports/unused
symbols are adjusted if necessary so the controller compiles cleanly after
deletion.

In `@src/proto/reaction.proto`:
- Around line 1-2: The proto package/location mismatch: reaction.proto declares
package "reaction" but lives under src/proto/, causing buf lint warnings; fix by
moving reaction.proto into a matching directory (e.g., create/update reaction/
and place reaction.proto there) or alternatively change the declared package in
reaction.proto to match the current directory layout; reference the file name
reaction.proto and the package declaration "package reaction" (and ensure any
related files like user.proto follow the same convention) so imports and buf
config remain consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fc14f0a6-f243-4af5-a4cb-95e7fab63f02

📥 Commits

Reviewing files that changed from the base of the PR and between be1474a and 64e4340.

📒 Files selected for processing (18)
  • src/cardset/application/cardset.use-case.ts
  • src/cardset/application/dto/request/create-cardset.request.ts
  • src/cardset/application/dto/request/update-cardset.request.ts
  • src/cardset/application/dto/response/cardset-list-item.response.ts
  • src/cardset/application/dto/response/cardset.response.ts
  • src/cardset/application/dto/response/manager-info.response.ts
  • src/cardset/cardset.module.ts
  • src/cardset/domain/repository/cardset-manager.repository.ts
  • src/cardset/domain/repository/cardset.repository.ts
  • src/cardset/infrastructure/grpc/reaction-grpc.client.ts
  • src/cardset/infrastructure/grpc/user-grpc.client.ts
  • src/cardset/infrastructure/http/card.controller.ts
  • src/cardset/infrastructure/http/cardset.controller.ts
  • src/cardset/infrastructure/http/group-cardset.controller.ts
  • src/cardset/infrastructure/persistence/cardset-manager.repository.impl.ts
  • src/cardset/infrastructure/persistence/cardset.repository.impl.ts
  • src/proto/reaction.proto
  • src/shared/grpc/grpc-client.module.ts

@stoneTiger0912 stoneTiger0912 merged commit 49507ac into main Mar 16, 2026
1 of 2 checks passed
@stoneTiger0912 stoneTiger0912 deleted the feat/add-group-in-cardset branch March 16, 2026 08:47
This was referenced Mar 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant