ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -13,15 +13,13 @@
 import nextstep.security.context.SecurityContextHolderFilter;
 import nextstep.security.matcher.AnyRequestMatcher;
 import nextstep.security.matcher.MvcRequestMatcher;
-import nextstep.security.matcher.RequestMatcherEntry;
 import nextstep.security.userdetails.UserDetails;
 import nextstep.security.userdetails.UserDetailsService;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
 import org.springframework.http.HttpMethod;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
@@ -50,20 +48,31 @@ public SecuredMethodInterceptor securedMethodInterceptor() {
         return new SecuredMethodInterceptor();
     }
 
+//    @Bean
+//    public SecuredAspect securedAspect() {
+//        return new SecuredAspect();
+//    }
+
+
     @Bean
-    public SecuredAspect securedAspect() {
-        return new SecuredAspect();
+    public RoleHierarchy roleHierarchy() {
+       return RoleHierarchyImpl.with()
+               .role("ADMIN")
+               .implies("USER","GUEST")
+               .build();
     }
 
+
     @Bean
     public RequestMatcherDelegatingAuthorizationManager requestAuthorizationManager() {
-        List<RequestMatcherEntry<AuthorizationManager>> mappings = new ArrayList<>();
-        mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/members/me"), new AuthenticatedAuthorizationManager()));
-        mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/members"), new HasAuthorityAuthorizationManager("ADMIN")));
-        mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/search"), new PermitAllAuthorizationManager()));
-        mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.POST, "/private"), new DenyAllAuthorizationManager()));
-        mappings.add(new RequestMatcherEntry<>(new AnyRequestMatcher(), new PermitAllAuthorizationManager()));
-        return new RequestMatcherDelegatingAuthorizationManager(mappings);
+        return RequestMatcherAuthorizationManagerBuilder.withRoleHierarchy(roleHierarchy())
+                .authenticated(new MvcRequestMatcher(HttpMethod.GET, "/members/me"))
+                .permitAll(new MvcRequestMatcher(HttpMethod.GET, "/search"))
+                .hasAuthority("ADMIN", new MvcRequestMatcher(HttpMethod.GET, "/members"))
+                .hasAuthority("USER", new MvcRequestMatcher(HttpMethod.POST, "/hierarchy"))
+                .denyAll(new MvcRequestMatcher(HttpMethod.POST, "/private"))
+                .permitAll(new AnyRequestMatcher())
+        .build();
     }
 
     @Bean

리뷰: 고민 끝에 빌더 클래스를 추출하여 개선해보았습니다!

[72fe548](https://github.com/next-step/spring-security-authorization/pull/23/commits/72fe5487d07f732b75950f6ce55af19c9dabd72b) 해당 커밋에 반영하였습니다.
@@ -2,33 +2,43 @@
 
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import nextstep.security.authentication.Authentication;
 import nextstep.security.context.SecurityContextHolder;
-import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.web.filter.GenericFilterBean;
 
 import java.io.IOException;
 
-public class CheckAuthenticationFilter extends OncePerRequestFilter {
+public class CheckAuthenticationFilter extends GenericFilterBean {
 
-    private final RequestMatcherDelegatingAuthorizationManager requestMatcherDelegatingAuthorizationManager;
+    private final AuthorizationManager<HttpServletRequest> authorizationManager;
+    private final AuthorizationDeniedHandler authorizationDeniedHandler;
 
-    public CheckAuthenticationFilter(RequestMatcherDelegatingAuthorizationManager requestMatcherDelegatingAuthorizationManager) {
-        this.requestMatcherDelegatingAuthorizationManager = requestMatcherDelegatingAuthorizationManager;
+    public CheckAuthenticationFilter(AuthorizationManager<HttpServletRequest> authorizationManager) {
+        this(authorizationManager, response -> response.setStatus(HttpServletResponse.SC_FORBIDDEN));
+    }
+
+    public CheckAuthenticationFilter(AuthorizationManager<HttpServletRequest> authorizationManager
+            , AuthorizationDeniedHandler authorizationDeniedHandler
+    ) {
+        this.authorizationManager = authorizationManager;
+        this.authorizationDeniedHandler = authorizationDeniedHandler;
     }
 
     @Override
-    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
         Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
 
-        AuthorizationDecision authorizationDecision = requestMatcherDelegatingAuthorizationManager.check(authentication, request);
+        AuthorizationDecision authorizationDecision = authorizationManager.check(authentication, (HttpServletRequest) request);
 
         if (authorizationDecision.isDeny()) {
-            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+            authorizationDeniedHandler.handle((HttpServletResponse) response);
             return;
         }
 
-        filterChain.doFilter(request, response);
+        chain.doFilter(request, response);
     }
 }

리뷰: 앗.. 그럴수도 있겠네요!

인가실패인 경우 유동적으로 처리할 수 있도록 인가 실패 핸들러 인터페이스를 정의하여 처리해보았습니다!

[fc99a7a](https://github.com/next-step/spring-security-authorization/pull/23/commits/fc99a7a726f73bf39d3a30e7f02f4d2ecab0ec4e) 해당 커밋에 반영해두었습니다
@@ -1,24 +0,0 @@
-package nextstep.security.authorization;
-
-import nextstep.security.authentication.Authentication;
-
-public class HasAuthorityAuthorizationManager implements AuthorizationManager {
-    private final String allowRole;
-
-    public HasAuthorityAuthorizationManager(String allowRole) {
-        this.allowRole = allowRole;
-    }
-
-    @Override
-    public AuthorizationDecision check(Authentication authentication, Object object) {
-        if (authentication == null || !authentication.isAuthenticated()) {
-            return AuthorizationDecision.deny();
-        }
-
-        if (authentication.getAuthorities().contains(allowRole)) {
-            return AuthorizationDecision.granted();
-        }
-
-        return AuthorizationDecision.deny();
-    }
-}

리뷰: 코드 구조를 분석해보니  AuthoritiesAuthorizationManager는 여러건의 인가를 체크할 수 있는 역할,
AuthorityAuthorizationManager 는  인가정보를 설정하는 역할로 분리한것 같아요. 

해당 부분을 관점으로 리팩토링 해보았습니다

[8ab4798](https://github.com/next-step/spring-security-authorization/pull/23/commits/8ab47981b3de163a355d9c4c50ef54f1cacf497c)해당 커밋에 반영해보았습니다
@@ -0,0 +1,24 @@
+package nextstep.security.authorization;
+
+import nextstep.security.authentication.Authentication;
+
+import java.util.Set;
+
+public class AuthorityAuthorizationManager<T> implements AuthorizationManager<T> {
+
+    private final Set<String> authorities;
+
+    public AuthorityAuthorizationManager(Set<String> authorities) {
+        this.authorities = authorities;
+    }
+
+    @Override
+    public AuthorizationDecision check(Authentication authentication, T object) {
+
+        boolean granted = authentication.getAuthorities()
+                .stream()
+                .anyMatch(authorities::contains);
+
+        return new AuthorizationDecision(granted);
+    }
+}

리뷰: 가변인자(String... authorities)를 사용하면 여러 권한을 더 쉽게 전달할 수 있을 것 같네요 😄 
`new AuthorityAuthorizationManager("ROLE_ADMIN", "ROLE_USER")`
@@ -89,4 +89,28 @@ void request_fail_invalid_password() throws Exception {
 
         response.andExpect(status().isUnauthorized());
     }
+
+    @DisplayName("인증된 사용자는 자신의 정보를 조회할 수 있다.")
+    @Test
+    void request_success_members_me() throws Exception {
+        String token = Base64.getEncoder().encodeToString((TEST_ADMIN_MEMBER.getEmail() + ":" + TEST_ADMIN_MEMBER.getPassword()).getBytes());
+
+        ResultActions response = mockMvc.perform(get("/members/me")
+                .header("Authorization", "Basic " + token)
+                .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+        );
+
+        response.andExpect(status().isOk())
+                .andExpect(MockMvcResultMatchers.jsonPath("$.email").value(TEST_ADMIN_MEMBER.getEmail()));
+    }
+
+    @DisplayName("인증되지 않은 사용자는 자신의 정보를 조회할 수 없다.")
+    @Test
+    void request_fail_members_me() throws Exception {
+        ResultActions response = mockMvc.perform(get("/members/me")
+                .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+        );
+
+        response.andExpect(status().isForbidden());
+    }
 }

리뷰: 테스트 코드 👍
@@ -0,0 +1,42 @@
+package nextstep.security.authorization;
+
+import nextstep.security.authentication.Authentication;
+import nextstep.security.context.SecurityContextHolder;
+import org.aopalliance.aop.Advice;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.aop.Pointcut;
+import org.springframework.aop.PointcutAdvisor;
+import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
+
+public class AuthorizationManagerBeforeMethodInterceptor implements MethodInterceptor, PointcutAdvisor {
+
+    private final AuthorizationManager<MethodInvocation> authorizationManager;
+
+    public AuthorizationManagerBeforeMethodInterceptor(AuthorizationManager<MethodInvocation> authorizationManager) {
+        this.authorizationManager = authorizationManager;
+    }
+
+    @Override
+    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
+
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        AuthorizationDecision decision = authorizationManager.check(authentication, methodInvocation);
+
+        if (decision != null && !decision.isGranted()) {
+            throw new ForbiddenException();
+        }
+
+        return methodInvocation.proceed();
+    }
+
+    @Override
+    public Pointcut getPointcut() {
+        return new AnnotationMatchingPointcut(null, Secured.class);
+    }
+
+    @Override
+    public Advice getAdvice() {
+        return this;
+    }
+}

리뷰: > Q.애노테이션을 활용한 인가 처리가 잘 이해가 되지 않아, SecuredAuthorizationManager를 만든 뒤 AuthorizationManagerBeforeMethodInterceptor에서 이를 가져다 사용하는 방향으로 구현했는데, 이게 괜찮은 방향인지는 잘 모르겠네요. 이부분에 대한 피드백을 한번 받아보고 싶습니다!

A. 잘 구현해 주셨네요! 👍 
AuthorizationManagerBeforeMethodInterceptor 에서 `@Secured`가 적용된 메서드를 감지하고, AuthorizationManager를 실행하여 사용자의 권한을 확인한 후 인가 여부를 결정하는 구조로 동작하는 방식으로 잘 구현해 주셨습니다.

다만, 다른 코멘트에서 남겼듯이 AuthoritiesAuthorizationManager 에 대해서는 한번 확인해 보시고 개선해 보면 좋을 것 같아요.
혹시 추가적으로 어려우신 부분이 있다면 남겨주시면 답변 드리도록 하겠습니다 😄
@@ -4,7 +4,7 @@ plugins {
     id 'io.spring.dependency-management' version '1.1.7'
 }
 
-group = 'com.example'
+group = 'com.tripmarket'
 version = '0.0.1-SNAPSHOT'
 
 java {
@@ -48,6 +48,7 @@ dependencies {
     // websocket
     implementation 'org.springframework.boot:spring-boot-starter-websocket'
     implementation 'org.webjars:sockjs-client:1.1.2'
+    implementation 'org.springframework:spring-websocket:6.1.2'
 
     // stomp
     implementation 'org.webjars:stomp-websocket:2.3.3-1'
@@ -58,8 +59,17 @@ dependencies {
     implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
     runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
     runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'
+
+    //rabbitmq
+    implementation 'org.springframework.boot:spring-boot-starter-amqp'
+    implementation 'org.springframework:spring-messaging:6.1.2'
+    implementation 'org.springframework.boot:spring-boot-starter-reactor-netty:3.0.0'
+//    implementation 'io.netty:netty-resolver-dns-native-macos:4.1.68.Final:osx-x86_64'
+
+    //mongodb
+    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
 }
 
 tasks.named('test') {
     useJUnitPlatform()
-}
+}
\ No newline at end of file

리뷰: 에러 뜨는거 때문에 추가했다가 뺀거죠? 그냥 무시하시면 됩니다 이친구는...
@@ -1,27 +1,47 @@
 package com.tripmarket.domain.chatting.entity;
 
+import java.time.LocalDateTime;
+
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.mongodb.core.index.CompoundIndex;
+import org.springframework.data.mongodb.core.index.CompoundIndexes;
+import org.springframework.data.mongodb.core.mapping.Document;
+
 import jakarta.persistence.*;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 
-import com.tripmarket.global.jpa.entity.BaseEntity;
-
-@Getter
-@NoArgsConstructor
 @Entity
-public class Message extends BaseEntity {
+@Builder
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Document(collection = "message")
+@CompoundIndexes({
+	@CompoundIndex(name = "room_createdAt_idx", def = "{'roomId': 1, 'createdAt': -1}"),
+	@CompoundIndex(name = "room_idx", def = "{'roomId': 1}"),
+	@CompoundIndex(name = "room_receiver_readStatus_idx",
+		def = "{'roomId': 1, 'receiver': 1, 'readStatus': 1}")
+})
+public class Message {
+
+	@Id
+	private String id;
 
 	private String roomId;
-	private String senderId;
-	private String toUserId;
+	private String sender;
+	private String receiver;
 	private String content;
+	private boolean readStatus;
+
+	@CreatedDate
+	@Column(name = "createdAt", updatable = false)
+	private LocalDateTime createdAt;
 
-	@Builder
-	public Message(String roomId, String senderId, String toUserId, String content) {
-		this.roomId = roomId;
-		this.senderId = senderId;
-		this.toUserId = toUserId;
-		this.content = content;
+	public void updateRead(boolean readStatus) {
+		this.readStatus = readStatus;
 	}
-}
+}
\ No newline at end of file

리뷰: MongoDB를 JPA처럼 쓰고 있는 것 같아요.
MongoDB는 Document-Based DB라는 특성을 생각해 보시면, 좀 더 잘 활용할 수 있을 것 같습니다.
@@ -1,27 +1,47 @@
 package com.tripmarket.domain.chatting.entity;
 
+import java.time.LocalDateTime;
+
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.mongodb.core.index.CompoundIndex;
+import org.springframework.data.mongodb.core.index.CompoundIndexes;
+import org.springframework.data.mongodb.core.mapping.Document;
+
 import jakarta.persistence.*;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 
-import com.tripmarket.global.jpa.entity.BaseEntity;
-
-@Getter
-@NoArgsConstructor
 @Entity
-public class Message extends BaseEntity {
+@Builder
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Document(collection = "message")
+@CompoundIndexes({
+	@CompoundIndex(name = "room_createdAt_idx", def = "{'roomId': 1, 'createdAt': -1}"),
+	@CompoundIndex(name = "room_idx", def = "{'roomId': 1}"),
+	@CompoundIndex(name = "room_receiver_readStatus_idx",
+		def = "{'roomId': 1, 'receiver': 1, 'readStatus': 1}")
+})
+public class Message {
+
+	@Id
+	private String id;
 
 	private String roomId;
-	private String senderId;
-	private String toUserId;
+	private String sender;
+	private String receiver;
 	private String content;
+	private boolean readStatus;
+
+	@CreatedDate
+	@Column(name = "createdAt", updatable = false)
+	private LocalDateTime createdAt;
 
-	@Builder
-	public Message(String roomId, String senderId, String toUserId, String content) {
-		this.roomId = roomId;
-		this.senderId = senderId;
-		this.toUserId = toUserId;
-		this.content = content;
+	public void updateRead(boolean readStatus) {
+		this.readStatus = readStatus;
 	}
-}
+}
\ No newline at end of file

리뷰: createdAt 이 아니라 id를 쓰는게 맞겠는데요?
mongodb의 objectId 의 특성을 학습하시고 오시면 좋을 것 같습니다.
@@ -3,6 +3,8 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
 import 'package:provider/provider.dart';
 import 'package:yum_application/src/app/page/app_page.dart';
 import 'package:yum_application/src/app/config/app_config.dart';
+import 'package:yum_application/src/ingredient/view/home_view.dart';
+import 'package:yum_application/src/recipe/view/review_view.dart';
 import 'package:yum_application/src/util/app_theme.dart';
 import 'package:yum_application/src/util/global_variable.dart';
 
@@ -23,7 +25,7 @@ class MyApp extends StatelessWidget {
     return MaterialApp(
       navigatorKey: GlobalVariable.naviagatorState,
       theme: light,
-      home: const AppPage(),
+      home: const ReviewView(),
     );
   }
 }

리뷰: 작업하신 뒤에는 main 변경사항을 원래대로 돌리셔야됩니다.
@@ -75,11 +75,12 @@ const endPoint = {
   postingDataForReviewRequestCode: `${serverUrl}/${VERSION2}/groups`,
   checkingPassword: `${serverUrl}/${VERSION2}/${REVIEW_PASSWORD_API_PARAMS.resource}/${REVIEW_PASSWORD_API_PARAMS.queryString.check}`,
   gettingReviewGroupData: (reviewRequestCode: string) =>
-    `${REVIEW_GROUP_DATA_API_URL}?${REVIEW_GROUP_DATA_API_PARAMS.queryString.reviewRequestCode}=${reviewRequestCode}`,
+    `${REVIEW_GROUP_DATA_API_URL}/summary?${REVIEW_GROUP_DATA_API_PARAMS.queryString.reviewRequestCode}=${reviewRequestCode}`,
   gettingSectionList: `${serverUrl}/${VERSION2}/sections`,
   gettingGroupedReviews: (sectionId: number) =>
     `${REVIEW_GROUP_API_URL}?${REVIEW_GROUP_API_PARAMS.queryString.sectionId}=${sectionId}`,
   postingHighlight: `${serverUrl}/${VERSION2}/highlight`,
+  gettingReviewLinks: REVIEW_GROUP_DATA_API_URL,
 };
 
 export default endPoint;

리뷰: gettingReviewLinks가 로그인한 사용자가 자신이 만든 리뷰 그룹 목록을 조회하는게 아닌가요?
자신이 만든 리뷰 그룹 목록을 조회하는거라면 다른 url로 요청을 보내야할 것 같은데요
![image](https://github.com/user-attachments/assets/b3d29016-6915-458d-bfab-3c06b693e78f)
@@ -4,6 +4,7 @@ import endPoint, { REVIEW_GROUP_DATA_API_PARAMS, REVIEW_GROUP_DATA_API_URL } fro
 import { API_ERROR_MESSAGE, INVALID_REVIEW_PASSWORD_MESSAGE } from '@/constants';
 import { getRequestBody } from '@/utils/mockingUtils';
 
+import { REVIEW_LINKS } from '../mockData';
 import {
   MOCK_AUTH_TOKEN_NAME,
   REVIEW_GROUP_DATA,
@@ -19,6 +20,18 @@ const postDataForReviewRequestCode = () => {
 
     if (bodyResult instanceof Error) return HttpResponse.json({ error: bodyResult.message }, { status: 400 });
     const { nonMember: nonMemberReviewRequestCode, member: memberReviewRequestCode } = VALID_REVIEW_REQUEST_CODE;
+
+    const newReviewLink = {
+      revieweeName: '쑤쑤',
+      projectName: '리뷰미2',
+      createdAt: '2025-05-10',
+      reviewRequestCode: memberReviewRequestCode,
+      reviewCount: 30,
+    };
+
+    // 새로 생성된 리뷰 링크를 목 데이터에 추가
+    REVIEW_LINKS.reviewGroups.push(newReviewLink);
+
     return HttpResponse.json(
       {
         reviewRequestCode: 'groupAccessCode' in bodyResult ? nonMemberReviewRequestCode : memberReviewRequestCode,
@@ -75,6 +88,14 @@ const getReviewGroupData = () => {
     const reviewRequestCode = params.get(queryString.reviewRequestCode);
 
     if (reviewRequestCode) {
+      const { nonMember: nonMemberReviewRequestCode } = VALID_REVIEW_REQUEST_CODE;
+
+      // 비회원일 경우, revieweeId를 null로 변경
+      if (reviewRequestCode === nonMemberReviewRequestCode) {
+        REVIEW_GROUP_DATA.revieweeId = null;
+        return HttpResponse.json(REVIEW_GROUP_DATA);
+      }
+
       return HttpResponse.json(REVIEW_GROUP_DATA);
     }
 

리뷰: 대문자 스네이크는 코드 컨벤션에서 변하지 않는 상수를 의미하기 때문에, 배열을 변형하는 push 동작과 맞지 않아요.
리뷰 링크 생성에 따른, 리뷰 목록의 변경을 확인하고 싶어서 넣은 것 같아요. 
그렇다면 ' REVIEW_LINKS'의 변수명을 바꿔야할 것 같아요.
@@ -4,6 +4,7 @@ import endPoint, { REVIEW_GROUP_DATA_API_PARAMS, REVIEW_GROUP_DATA_API_URL } fro
 import { API_ERROR_MESSAGE, INVALID_REVIEW_PASSWORD_MESSAGE } from '@/constants';
 import { getRequestBody } from '@/utils/mockingUtils';
 
+import { REVIEW_LINKS } from '../mockData';
 import {
   MOCK_AUTH_TOKEN_NAME,
   REVIEW_GROUP_DATA,
@@ -19,6 +20,18 @@ const postDataForReviewRequestCode = () => {
 
     if (bodyResult instanceof Error) return HttpResponse.json({ error: bodyResult.message }, { status: 400 });
     const { nonMember: nonMemberReviewRequestCode, member: memberReviewRequestCode } = VALID_REVIEW_REQUEST_CODE;
+
+    const newReviewLink = {
+      revieweeName: '쑤쑤',
+      projectName: '리뷰미2',
+      createdAt: '2025-05-10',
+      reviewRequestCode: memberReviewRequestCode,
+      reviewCount: 30,
+    };
+
+    // 새로 생성된 리뷰 링크를 목 데이터에 추가
+    REVIEW_LINKS.reviewGroups.push(newReviewLink);
+
     return HttpResponse.json(
       {
         reviewRequestCode: 'groupAccessCode' in bodyResult ? nonMemberReviewRequestCode : memberReviewRequestCode,
@@ -75,6 +88,14 @@ const getReviewGroupData = () => {
     const reviewRequestCode = params.get(queryString.reviewRequestCode);
 
     if (reviewRequestCode) {
+      const { nonMember: nonMemberReviewRequestCode } = VALID_REVIEW_REQUEST_CODE;
+
+      // 비회원일 경우, revieweeId를 null로 변경
+      if (reviewRequestCode === nonMemberReviewRequestCode) {
+        REVIEW_GROUP_DATA.revieweeId = null;
+        return HttpResponse.json(REVIEW_GROUP_DATA);
+      }
+
       return HttpResponse.json(REVIEW_GROUP_DATA);
     }
 

리뷰: 이것도 앞에서 언급한 이유로, 변수명을 바꿔야겠어요.
@@ -75,11 +75,12 @@ const endPoint = {
   postingDataForReviewRequestCode: `${serverUrl}/${VERSION2}/groups`,
   checkingPassword: `${serverUrl}/${VERSION2}/${REVIEW_PASSWORD_API_PARAMS.resource}/${REVIEW_PASSWORD_API_PARAMS.queryString.check}`,
   gettingReviewGroupData: (reviewRequestCode: string) =>
-    `${REVIEW_GROUP_DATA_API_URL}?${REVIEW_GROUP_DATA_API_PARAMS.queryString.reviewRequestCode}=${reviewRequestCode}`,
+    `${REVIEW_GROUP_DATA_API_URL}/summary?${REVIEW_GROUP_DATA_API_PARAMS.queryString.reviewRequestCode}=${reviewRequestCode}`,
   gettingSectionList: `${serverUrl}/${VERSION2}/sections`,
   gettingGroupedReviews: (sectionId: number) =>
     `${REVIEW_GROUP_API_URL}?${REVIEW_GROUP_API_PARAMS.queryString.sectionId}=${sectionId}`,
   postingHighlight: `${serverUrl}/${VERSION2}/highlight`,
+  gettingReviewLinks: REVIEW_GROUP_DATA_API_URL,
 };
 
 export default endPoint;

리뷰: 리뷰 그룹 생성, 리뷰 그룹 목록 조회 url이 같아서 저 상수를 사용했어요.
`REVIEW_GROUP_DATA_API_URL` === `https://api.review-me.page/v2/groups`
@@ -4,6 +4,7 @@ import endPoint, { REVIEW_GROUP_DATA_API_PARAMS, REVIEW_GROUP_DATA_API_URL } fro
 import { API_ERROR_MESSAGE, INVALID_REVIEW_PASSWORD_MESSAGE } from '@/constants';
 import { getRequestBody } from '@/utils/mockingUtils';
 
+import { REVIEW_LINKS } from '../mockData';
 import {
   MOCK_AUTH_TOKEN_NAME,
   REVIEW_GROUP_DATA,
@@ -19,6 +20,18 @@ const postDataForReviewRequestCode = () => {
 
     if (bodyResult instanceof Error) return HttpResponse.json({ error: bodyResult.message }, { status: 400 });
     const { nonMember: nonMemberReviewRequestCode, member: memberReviewRequestCode } = VALID_REVIEW_REQUEST_CODE;
+
+    const newReviewLink = {
+      revieweeName: '쑤쑤',
+      projectName: '리뷰미2',
+      createdAt: '2025-05-10',
+      reviewRequestCode: memberReviewRequestCode,
+      reviewCount: 30,
+    };
+
+    // 새로 생성된 리뷰 링크를 목 데이터에 추가
+    REVIEW_LINKS.reviewGroups.push(newReviewLink);
+
     return HttpResponse.json(
       {
         reviewRequestCode: 'groupAccessCode' in bodyResult ? nonMemberReviewRequestCode : memberReviewRequestCode,
@@ -75,6 +88,14 @@ const getReviewGroupData = () => {
     const reviewRequestCode = params.get(queryString.reviewRequestCode);
 
     if (reviewRequestCode) {
+      const { nonMember: nonMemberReviewRequestCode } = VALID_REVIEW_REQUEST_CODE;
+
+      // 비회원일 경우, revieweeId를 null로 변경
+      if (reviewRequestCode === nonMemberReviewRequestCode) {
+        REVIEW_GROUP_DATA.revieweeId = null;
+        return HttpResponse.json(REVIEW_GROUP_DATA);
+      }
+
       return HttpResponse.json(REVIEW_GROUP_DATA);
     }
 

리뷰: 아....!!!! 맞네요... 꼼꼼하십니다!
수정하겠습니다!
@@ -10,8 +10,9 @@ export const REVIEW_MESSAGE = {
 };
 
 export const REVIEW_EMPTY = {
-  noReviewInTotal: '아직 받은 리뷰가 없어요!',
-  noReviewInQuestion: '이 질문은 아직 받은 답변이 없어요!',
+  noReviewInTotal: '아직 받은 리뷰가 없어요...',
+  noReviewLink: '생성한 리뷰 링크가 없어요...',
+  noReviewInQuestion: '이 질문은 아직 받은 답변이 없어요...',
 };
 
 export const REVIEW_URL_GENERATOR_FORM_VALIDATION = {

리뷰: (원래 있었던 상수긴 하지만) 이 값들을 상수로 관리할 필요가 있을까요?
각 페이지나 컴포넌트에 특화된 값들이고 재사용성도 딱히 고려할 필요 없을 것 같아서 공통 상수로 뺄 필요가 없다는 생각입니다. 또 당장 EmptyContent 컴포넌트도 안내 문구를 string으로만 받지 않고 children으로 받으니까요. 

당장 수정하자는 건 아닙니다ㅎㅎ