Skip to content

Feat/58/shared refrigerator#59

Merged
chan0831 merged 12 commits intoreleasefrom
feat/58/sharedRefrigerator
Dec 15, 2025
Merged

Feat/58/shared refrigerator#59
chan0831 merged 12 commits intoreleasefrom
feat/58/sharedRefrigerator

Conversation

@chan0831
Copy link
Contributor

@chan0831 chan0831 commented Dec 15, 2025

🚀 변경사항

공유 냉장고

🔗 관련 이슈

  • Closes

✅ 체크리스트

  • 로컬에서 테스트 완료
  • 코드 리뷰 준비 완료

📝 특이사항

Summary by CodeRabbit

  • New Features

    • Added refrigerator invitation system enabling users to invite followed members to share a refrigerator
    • Added real-time WebSocket notifications for ingredient updates
    • Enhanced follower/following lists to display invitation status and mutual follow details
    • Added invitation management endpoints to accept, reject, or cancel refrigerator invitations
  • Chores

    • Added WebSocket dependency to build configuration

✏️ Tip: You can customize this high-level summary in your review settings.

@chan0831 chan0831 self-assigned this Dec 15, 2025
@chan0831 chan0831 added the ✨ feature add feature label Dec 15, 2025
@chan0831 chan0831 linked an issue Dec 15, 2025 that may be closed by this pull request
1 task
@coderabbitai
Copy link

coderabbitai bot commented Dec 15, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Implements refrigerator sharing and real-time updates: converts Member–Refrigerator to a many-to-one relationship, adds RefrigeratorInvitation entity and services (command/query), extends follow APIs to include invitation status, introduces WebSocket STOMP (with JWT interceptor), and updates converters/DTOs and controllers to support invitations and broadcast ingredient updates.

Changes

Cohort / File(s) Summary
Build & WebSocket infra
build.gradle, src/main/java/novaminds/gradproj/config/WebSocketConfig.java, src/main/java/novaminds/gradproj/global/websocket/StompHandler.java, src/main/java/novaminds/gradproj/config/SecurityConfig.java, src/main/java/novaminds/gradproj/domain/member/service/security/auth/ProfileCompletionFilter.java
Added Spring WebSocket starter; new WebSocketConfig enabling STOMP (/ws-stomp), broker prefixes /sub & /pub, SockJS and CORS; StompHandler validates JWT on CONNECT; security and profile filter exempt /ws-stomp/**.
Domain model: Refrigerator ↔ Member
src/main/java/novaminds/gradproj/domain/member/entity/Member.java, src/main/java/novaminds/gradproj/domain/refrigerator/entity/Refrigerator.java
Replaced one-to-one mapping with collection-based relationship: Member now has @ManyToOne refrigerator; Refrigerator holds @OneToMany List<Member> memberList; added add/remove member methods and adjusted setRefrigerator logic; removed unique constraint.
Invitation entity & repo
src/main/java/novaminds/gradproj/domain/refrigerator/entity/RefrigeratorInvitation.java, src/main/java/novaminds/gradproj/domain/refrigerator/repository/RefrigeratorInvitationRepository.java
New RefrigeratorInvitation JPA entity with InvitationStatus (PENDING/ACCEPTED/REJECTED/CANCELED) and lifecycle methods; repository added with query methods to find by inviter/invitee/status and batch queries.
Invitation command/query services
src/main/java/novaminds/gradproj/domain/refrigerator/service/command/RefrigeratorInvitationCommandService.java, src/main/java/novaminds/gradproj/domain/refrigerator/service/query/RefrigeratorInvitationQueryService.java
New services: command service handles send/accept/reject/cancel flows (validations, member transfer, deletion of empty refrigerators); query service returns received/sent pending invitations via repository and converter.
Refrigerator command updates (WebSocket)
src/main/java/novaminds/gradproj/domain/refrigerator/service/command/RefrigeratorCommandService.java
Added SimpMessagingTemplate broadcasts (SocketMessage) to notify /sub/refrigerator/{id} on ingredient changes; removed setting member at creation.
Converter & DTO changes
src/main/java/novaminds/gradproj/domain/refrigerator/converter/RefrigeratorConverter.java, src/main/java/novaminds/gradproj/domain/refrigerator/web/dto/RefrigeratorResponseDTO.java, src/main/java/novaminds/gradproj/domain/refrigerator/web/dto/RefrigeratorRequestDTO.java
Added invitation conversion helpers and InvitationResponse/List DTOs; added refrigeratorId to IngredientResponse and StoredIngredientCount; added InvitationRequest DTO for invite payload.
Repository & onboarding adjustments
src/main/java/novaminds/gradproj/domain/refrigerator/repository/RefrigeratorRepository.java, src/main/java/novaminds/gradproj/domain/member/service/MemberOnboardingService.java
Removed existsByMember from RefrigeratorRepository; MemberOnboardingService no longer depends on RefrigeratorRepository and checks member.getRefrigerator() != null.
Follow repositories & query service
src/main/java/novaminds/gradproj/domain/member/repository/FollowRepository.java, src/main/java/novaminds/gradproj/domain/member/repository/FollowRepositoryCustom.java, src/main/java/novaminds/gradproj/domain/member/repository/FollowRepositoryCustomImpl.java, src/main/java/novaminds/gradproj/domain/member/service/query/FollowQueryService.java
Extended FollowRepository with custom interface and QueryDSL implementation to fetch followers/followings with member and refrigerator via fetch joins; new FollowQueryService computes mutuals, batches pending invitations, and determines invitation status.
Member controller & DTOs
src/main/java/novaminds/gradproj/domain/member/web/controller/MemberController.java, src/main/java/novaminds/gradproj/domain/member/web/dto/MemberResponseDTO.java
MemberController wired with FollowQueryService and exposes GET /api/members/followers and /api/members/followings; added FollowMemberInfo, FollowersResponse, FollowingsResponse DTOs with InvitationStatus enum.
Error codes
src/main/java/novaminds/gradproj/apiPayload/code/status/ErrorStatus.java
Added invitation-related error constants (INVITATION_NOT_FOUND, INVITATION_ALREADY_EXISTS, INVITATION_ALREADY_PROCESSED, INVITATION_NOT_AUTHORIZED, CANNOT_INVITE_SELF, ALREADY_IN_SAME_REFRIGERATOR).
Refrigerator invitation controller
src/main/java/novaminds/gradproj/domain/refrigerator/web/controller/RefrigeratorInvitationController.java
New REST controller /api/refrigerators/invitations with endpoints to send, list received/sent, accept, reject, and cancel invitations; uses command/query services and ApiResponse wrapping.
sequenceDiagram
    participant Client as Web Client
    participant WS as STOMP Endpoint (/ws-stomp)
    participant StompHandler as StompHandler (JWT)
    participant Controller as RefrigeratorInvitationController
    participant CmdService as RefrigeratorInvitationCommandService
    participant InviteRepo as RefrigeratorInvitationRepository
    participant RefrigRepo as RefrigeratorRepository
    participant MemberRepo as MemberRepository

    Client->>WS: CONNECT (Authorization: Bearer <token>)
    WS->>StompHandler: preSend(CONNECT)
    StompHandler-->>WS: VALID / REJECT

    Client->>Controller: POST /api/refrigerators/invitations/{nickname}/send
    Controller->>CmdService: sendInvitation(inviter, nickname)
    CmdService->>MemberRepo: findByNickname(nickname)
    MemberRepo-->>CmdService: invitee
    CmdService->>InviteRepo: findByInviterAndInviteeAndStatus(...)
    InviteRepo-->>CmdService: Optional.empty()/existing
    CmdService->>InviteRepo: save(new RefrigeratorInvitation(...))
    CmdService-->>Controller: success
    Controller-->>Client: ApiResponse(success)

    Note over CmdService,RefrigRepo: On accept flow, CmdService transfers member between refrigerators and may delete empty refrigerator, then updates invitation status and broadcasts via SimpMessagingTemplate to /sub/refrigerator/{id}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Areas needing extra attention:
    • Member–Refrigerator relationship change: cascade, lazy loading, DB schema migration and unique constraint removal.
    • RefrigeratorInvitationCommandService: transaction boundaries, authorization checks, and deletion of empty refrigerators.
    • FollowQueryService & FollowRepositoryCustomImpl: correctness of QueryDSL fetch-joins to avoid N+1 and unintended eager loads.
    • WebSocket security: StompHandler token extraction/validation and security/filter exemptions.
    • Converter/DTO signature changes affecting multiple callers.

Possibly related PRs

  • PR #45: Overlaps converter and RefrigeratorQueryService mapping changes (refrigeratorId parameter and invitation conversion).
  • PR #34: Alters Member ↔ Refrigerator domain model and related services—strong overlap with relationship refactor here.
  • PR #41: Modifies ErrorStatus enum with related constants; potential conflicts in error-code additions.

Poem

🐰 I hopped in code and made a bridge so wide,
Invitations flutter, followers decide,
STOMP and sockets hum when carrots shift,
Members gather round the shared cold rift—
Hooray, the fridge is full and friends inside! 🥕🥬

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/58/sharedRefrigerator

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37724ec and 9871677.

📒 Files selected for processing (26)
  • build.gradle (1 hunks)
  • src/main/java/novaminds/gradproj/apiPayload/code/status/ErrorStatus.java (1 hunks)
  • src/main/java/novaminds/gradproj/config/SecurityConfig.java (1 hunks)
  • src/main/java/novaminds/gradproj/config/WebSocketConfig.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/member/entity/Member.java (2 hunks)
  • src/main/java/novaminds/gradproj/domain/member/repository/FollowRepository.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/member/repository/FollowRepositoryCustom.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/member/repository/FollowRepositoryCustomImpl.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/member/service/MemberOnboardingService.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/member/service/query/FollowQueryService.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/member/service/security/auth/ProfileCompletionFilter.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/member/web/controller/MemberController.java (3 hunks)
  • src/main/java/novaminds/gradproj/domain/member/web/dto/MemberResponseDTO.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/refrigerator/converter/RefrigeratorConverter.java (4 hunks)
  • src/main/java/novaminds/gradproj/domain/refrigerator/entity/Refrigerator.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/refrigerator/entity/RefrigeratorInvitation.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/refrigerator/repository/RefrigeratorInvitationRepository.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/refrigerator/repository/RefrigeratorRepository.java (0 hunks)
  • src/main/java/novaminds/gradproj/domain/refrigerator/service/command/RefrigeratorCommandService.java (6 hunks)
  • src/main/java/novaminds/gradproj/domain/refrigerator/service/command/RefrigeratorInvitationCommandService.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/refrigerator/service/query/RefrigeratorInvitationQueryService.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/refrigerator/service/query/RefrigeratorQueryService.java (2 hunks)
  • src/main/java/novaminds/gradproj/domain/refrigerator/web/controller/RefrigeratorInvitationController.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/refrigerator/web/dto/RefrigeratorRequestDTO.java (1 hunks)
  • src/main/java/novaminds/gradproj/domain/refrigerator/web/dto/RefrigeratorResponseDTO.java (3 hunks)
  • src/main/java/novaminds/gradproj/global/websocket/StompHandler.java (1 hunks)

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.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @chan0831, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 공유 냉장고 기능을 도입하여 여러 사용자가 하나의 냉장고를 함께 관리할 수 있도록 합니다. 이를 위해 핵심 엔티티 관계를 재정의하고, 초대 발송, 수락, 거절, 취소 등 초대 라이프사이클을 관리하는 시스템을 구축했습니다. 또한, WebSocket을 활용하여 냉장고 내용물 변경 시 모든 공유 멤버에게 실시간으로 업데이트를 제공함으로써 협업 경험을 향상시켰습니다.

Highlights

  • 공유 냉장고 기능 도입: 여러 사용자가 하나의 냉장고를 공유할 수 있도록 Member와 Refrigerator 엔티티 간의 관계를 1:1에서 N:1로 변경하고, 냉장고 초대 시스템을 구현했습니다.
  • WebSocket을 통한 실시간 업데이트: Spring Boot WebSocket을 통합하여 냉장고 재료 추가, 수정, 삭제 시 공유 냉장고의 모든 멤버에게 실시간으로 변경 사항을 알리는 기능을 추가했습니다.
  • 냉장고 초대 시스템: 사용자가 다른 사용자를 냉장고에 초대하고, 초대를 수락, 거절, 취소할 수 있는 완전한 초대 관리 기능을 구현했습니다.
  • 새로운 에러 코드 및 보안 설정: 냉장고 초대 관련 에러 상태 코드를 추가하고, WebSocket 엔드포인트에 대한 보안 설정을 업데이트했습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

공유 냉장고 기능 구현을 위한 PR 잘 보았습니다. 전반적으로 기능 구현이 잘 이루어진 것 같습니다. 특히 웹소켓을 이용한 실시간 업데이트와 초대 기능 구현이 인상적입니다. 몇 가지 개선점과 잠재적인 위험에 대해 리뷰 코멘트를 남겼으니 확인 부탁드립니다. 주요 내용으로는 Refrigerator 엔티티의 CascadeType 설정, NullPointerException 발생 가능성, 코드 중복, 그리고 API 엔드포인트 설계에 대한 제안이 있습니다.

public void registerStompEndpoints(StompEndpointRegistry registry) {
// 웹소켓 연결 주소: ws://localhost:8080/ws-stomp
registry.addEndpoint("/ws-stomp")
.setAllowedOriginPatterns("*") // CORS 허용
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

.setAllowedOriginPatterns("*")를 사용하면 모든 출처(origin)에서의 웹소켓 연결을 허용하게 됩니다. 개발 중에는 편리할 수 있지만, 프로덕션 환경에서는 보안상 위험할 수 있습니다. 프론트엔드 애플리케이션의 특정 출처만 허용하도록 제한하여, 허가되지 않은 웹사이트가 웹소켓 엔드포인트에 연결하는 것을 방지하는 것이 좋습니다.

Suggested change
.setAllowedOriginPatterns("*") // CORS 허용
.setAllowedOriginPatterns("https://your-frontend-domain.com") // TODO: 프로덕션 환경에 맞게 도메인을 제한해주세요.

Comment on lines +108 to +125
public void rejectInvitation(Member invitee, Long invitationId) {
// 초대 조회
RefrigeratorInvitation invitation = invitationRepository.findById(invitationId)
.orElseThrow(() -> new GeneralException(ErrorStatus.INVITATION_NOT_FOUND));

// 초대받은 사람이 맞는지 확인
if (!invitation.getInvitee().getLoginId().equals(invitee.getLoginId())) {
throw new GeneralException(ErrorStatus.INVITATION_NOT_AUTHORIZED);
}

// 이미 처리된 초대인지 확인
if (invitation.getStatus() != InvitationStatus.PENDING) {
throw new GeneralException(ErrorStatus.INVITATION_ALREADY_PROCESSED);
}

// 초대 거절
invitation.reject();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

acceptInvitation, rejectInvitation, cancelInvitation 메소드 전반에 걸쳐 초대 정보를 조회하고 유효성을 검사하는 (존재 여부, 권한, 상태 확인) 로직이 중복되고 있습니다. 이렇게 코드가 중복되면 향후 유지보수가 어려워질 수 있습니다. 이 공통 로직을 별도의 private 헬퍼 메소드로 추출하는 것을 고려해보세요.

예를 들어, 다음과 같은 헬퍼 메소드를 만들 수 있습니다:

private RefrigeratorInvitation findAndValidateInvitation(Long invitationId, Member member, boolean isInvitee) {
    RefrigeratorInvitation invitation = invitationRepository.findById(invitationId)
            .orElseThrow(() -> new GeneralException(ErrorStatus.INVITATION_NOT_FOUND));

    Member principal = isInvitee ? invitation.getInvitee() : invitation.getInviter();
    if (!principal.getLoginId().equals(member.getLoginId())) {
        throw new GeneralException(ErrorStatus.INVITATION_NOT_AUTHORIZED);
    }

    if (invitation.getStatus() != InvitationStatus.PENDING) {
        throw new GeneralException(ErrorStatus.INVITATION_ALREADY_PROCESSED);
    }
    return invitation;
}

이렇게 하면 rejectInvitation 메소드는 다음과 같이 간결해집니다:

public void rejectInvitation(Member invitee, Long invitationId) {
    RefrigeratorInvitation invitation = findAndValidateInvitation(invitationId, invitee, true);
    invitation.reject();
}

Comment on lines +33 to +40
@PostMapping("/{nickname}/send")
public ApiResponse<String> sendInvitation(
@CurrentUser Member member,
@PathVariable String nickname
) {
invitationCommandService.sendInvitation(member, nickname);
return ApiResponse.onSuccess("초대를 보냈습니다.");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

sendInvitation 엔드포인트가 초대받는 사람의 nickname을 URL 경로 변수(@PathVariable)로 받고 있습니다. 이 방식도 동작은 하지만, 리소스(초대)를 생성하는 POST 요청에서는 일반적으로 요청 본문(@RequestBody)을 사용하는 것이 더 표준적인 REST API 설계 방식입니다.

특히 현재 RefrigeratorRequestDTO.InvitationRequest라는 DTO가 정의되어 있지만 사용되지 않고 있습니다. 이 DTO를 활용하여 API의 일관성을 높이는 것이 좋습니다.

엔드포인트를 POST /api/refrigerators/invitations로 변경하고, 메소드 시그니처를 다음과 같이 수정하는 것을 고려해보세요:

@PostMapping
public ApiResponse<String> sendInvitation(
        @CurrentUser Member member,
        @RequestBody @Valid RefrigeratorRequestDTO.InvitationRequest request
) {
    invitationCommandService.sendInvitation(member, request.getInviteeNickname());
    return ApiResponse.onSuccess("초대를 보냈습니다.");
}

이렇게 하면 API가 더 일관성 있어지고, 나중에 초대 요청에 파라미터가 추가될 경우 확장하기에도 용이합니다.

chan0831 and others added 2 commits December 15, 2025 18:36
…ommand/RefrigeratorInvitationCommandService.java

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…frigerator.java

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@chan0831 chan0831 merged commit 670e5fe into release Dec 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feature add feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 공유 냉장고 기능 추가

2 participants

Comments