Skip to content

Conversation

@GwanLiZa
Copy link
Contributor

@GwanLiZa GwanLiZa commented Sep 6, 2025

changePresident.webm

Summary by CodeRabbit

  • 신기능

    • 멤버 관리에서 그룹장 변경 기능 추가(변경 전 확인 대화상자 제공).
    • 그룹장 전용 표시 배지 및 행 비편집 처리(강제 추방/역할 변경 불가).
    • 비그룹장 사용자는 ‘그룹장’ 역할 선택 옵션이 숨겨집니다.
  • 개선

    • 다국어 문자열 추가(EN/JP/KO/RU): 중복 그룹명 오류, 노션 링크 미입력 안내, 그룹장 라벨 및 변경 안내 문구.
    • 멤버 목록에 현재 사용자와 그룹장 식별 정보를 함께 로드하여 맥락 제공.
  • 스타일/UX

    • 그룹장 행에 강조 스타일 적용으로 가시성 향상.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 6, 2025

📝 Walkthrough

Walkthrough

그룹 관리 기능에 대통령(president) 역할과 회장 변경 플로우가 추가되었고, 권한 필드 명칭이 authorities에서 permissions로 일괄 변경되었습니다. i18n 문자열이 다국어(EN/JP/KO/RU)로 보강되었으며, Bloc/페이지/위젯/UI, 도메인/데이터 모델, API/리포지토리 계층이 이에 맞춰 수정·추가되었습니다.

Changes

Cohort / File(s) Summary
i18n 문자열 추가
assets/i18n/strings.i18n.json, assets/i18n/strings_jp.i18n.json, assets/i18n/strings_ko.i18n.json, assets/i18n/strings_ru.i18n.json
그룹명 중복 에러, 노션 링크 미입력 안내, role.president 라벨, 회장 변경 확인 타이틀/설명 추가. 키 경로 일부는 group.manage vs group.management 차이 확인 필요.
역할 모델 필드명 변경 (authorities → permissions)
lib/.../data/data_sources/models/create_role_model.dart, lib/.../data/data_sources/models/role_model.dart, lib/.../data/data_sources/models/update_role_model.dart, lib/.../domain/entities/authority_entity.dart, lib/.../domain/entities/role_entity.dart
공개 생성자/필드에서 authorities를 permissions로 일괄 변경. 타입은 List 유지.
회장 변경 데이터 모델/API 추가
lib/.../data/data_sources/models/update_president_model.dart, lib/.../data/data_sources/remote/group_api.dart, lib/.../data/repositories/rest_group_repository.dart, lib/.../domain/repository/group_repository.dart
UpdatePresidentModel 신설, GroupApi.updatePresident 본문 타입을 Map→UpdatePresidentModel로 변경, 리포지토리에 changePresident 추가, 도메인 인터페이스에 메서드 선언 추가.
역할 enum 확장
lib/.../data/enums/group_member_role.dart
enum 값 president 추가(@jsonvalue), toInt/로컬라이즈/보조 메서드 isPresident 추가.
Bloc 상태/이벤트 확장 및 주입 의존성 추가
lib/.../presentation/blocs/group_member_bloc.dart
RestAuthRepository 의존성 추가. getMembers 시 내 UUID와 회장 UUID를 함께 로드. 이벤트 changePresident 추가. loaded 상태가 (members, myUuid, presidentUuid)로 확장.
페이지/UI 회장 변경 플로우 연결
lib/.../presentation/pages/group_management_member_page.dart
BlocProvider 제너릭/즉시 초기화 적용, getMembers 재디스패치. role 변경 시 president 선택 시 확인 다이얼로그 후 changePresident 디스패치. GroupMemberCard.editMode에 uuid/myUuid/presidentUuid 전달.
멤버 카드 위젯 확장
lib/.../presentation/widgets/group_member_card.dart
uuid/myUuid/presidentUuid 필드 추가. 회장 행은 드롭다운 대신 배지 표시, 회장일 경우 추방 버튼 숨김. 현재 사용자가 회장 아닐 때 president 선택 항목 필터링.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant UI as GroupManagementMemberPage
  participant B as GroupMemberBloc
  participant R as RestGroupRepository
  participant A as GroupApi
  participant S as Server

  U->>UI: 멤버 목록 화면 진입
  UI->>B: getMembers(groupUuid)
  B->>R: getMembers(groupUuid)
  R->>A: GET /groups/{uuid}/members
  A->>S: Request
  S-->>A: members
  A-->>R: members
  B->>R: getGroup(groupUuid)
  R->>A: GET /groups/{uuid}
  A->>S: Request
  S-->>A: group (presidentUuid)
  A-->>R: group
  B->>Auth: getMyUuid()
  B-->>UI: state.loaded(members, myUuid, presidentUuid)
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant UI as GroupManagementMemberPage
  participant B as GroupMemberBloc
  participant R as RestGroupRepository
  participant A as GroupApi
  participant S as Server

  U->>UI: 멤버 역할을 president로 선택
  UI->>U: 회장 변경 확인 다이얼로그
  U-->>UI: 확인
  UI->>B: changePresident(groupUuid, targetUuid)
  B->>R: changePresident(uuid, targetUuid)
  R->>A: PUT /groups/{uuid}/president\nBody: UpdatePresidentModel(targetUuid)
  A->>S: Request
  S-->>A: 204 No Content
  A-->>R: ok
  R-->>B: ok
  B-->>UI: state.success
  B->>B: getMembers 재조회
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

Poem

귀가 쫑긋, 깃발 펄럭, 권한은 permissions!
회장은 바뀌고, 드롭다운은 살짝 숨김 🐇
블록은 말한다: loaded, 나와 회장 UUID!
번역도 척척, JP·KO·RU로 퐁퐁—
오늘도 토끼는 PR 밭에서 캬롯 승진 중 🥕

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@GwanLiZa GwanLiZa linked an issue Sep 6, 2025 that may be closed by this pull request
Copy link
Contributor

@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)
lib/app/modules/groups/presentation/blocs/group_member_bloc.dart (2)

31-38: removeMember 핸들러: 실패 시 무한 로딩/크래시 가능 + 리스트 갱신 누락

예외 미처리로 에러 시 로딩 상태에 머물 수 있고, 성공해도 목록을 다시 불러오지 않습니다.

     on<_RemoveMember>((event, emit) async {
-      emit(GroupMemberState.loading());
-      await _repository.removeMember(
-        uuid: event.uuid,
-        targetUuid: event.targetUuid,
-      );
-      emit(GroupMemberState.success());
+      emit(GroupMemberState.loading());
+      try {
+        await _repository.removeMember(
+          uuid: event.uuid,
+          targetUuid: event.targetUuid,
+        );
+        emit(GroupMemberState.success());
+        add(GroupMemberEvent.getMembers(event.uuid));
+      } on Exception catch (e) {
+        emit(GroupMemberState.error(e.toString()));
+      }
     });

39-57: grantRoleToUser: 에러 후에도 success 상태를 내보내는 논리 버그

catch 이후에도 successgetMembers를 실행합니다. 실패가 성공으로 보이는 치명적 UX 버그입니다.

     on<_GrantRoleToUser>((event, emit) async {
       emit(GroupMemberState.loading());
       try {
         await _repository.removeRoleFromUser(
           uuid: event.uuid,
           targetUuid: event.targetUuid,
           roleId: event.previousRole.toInt(),
         );
         await _repository.grantRoleToUser(
           uuid: event.uuid,
           targetUuid: event.targetUuid,
           roleId: event.role.toInt(),
         );
-      } on Exception catch (e) {
-        emit(_Error(e.toString()));
-      }
-      emit(GroupMemberState.success());
-      add(GroupMemberEvent.getMembers(event.uuid));
+        emit(GroupMemberState.success());
+        add(GroupMemberEvent.getMembers(event.uuid));
+      } on Exception catch (e) {
+        emit(GroupMemberState.error(e.toString()));
+      }
     });
🧹 Nitpick comments (23)
lib/app/modules/groups/domain/repository/group_repository.dart (1)

52-55: changePresident 파라미터 의미를 더 명확히 (네이밍/계약 주석 추천)

uuid가 그룹 UUID인지 사용자 UUID인지 혼동될 수 있습니다. 모델(UpdatePresidentModel.newPresidentUuid)과 의미를 맞추고, 사용/실패 조건(권한 없음 403, 동시 갱신 409 등)을 dartdoc로 명시하면 좋겠습니다.

가능한 개선안:

-  Future<void> changePresident({
-    required String uuid,
-    required String targetUuid,
-  });
+  /// 그룹의 대통령을 새 사용자로 변경합니다.
+  /// - [groupUuid]: 대상 그룹의 UUID
+  /// - [newPresidentUuid]: 새 대통령이 될 사용자 UUID
+  /// 실패 상황: 403(권한 없음), 404(그룹/사용자 없음), 409(동시 변경 충돌) 등
+  Future<void> changePresident({
+    required String groupUuid,
+    required String newPresidentUuid,
+  });

검증 제안:

  • UI/BLoC에서 나 자신(myUuid)이 현 대통령이 아닐 때 액션 비노출 확인.
  • 동일 사용자로 재지정 시 서버가 멱등 처리(무변경)하는지 확인.
lib/app/modules/groups/data/data_sources/models/role_model.dart (1)

14-19: RoleModel.fromJson에 legacy authorities 키 호환 처리 추가
fromJson 입력을 복사해 permissions가 없고 authorities가 있을 때 대응하도록 변경하세요.

- factory RoleModel.fromJson(Map<String, dynamic> json) =>
-     _$RoleModelFromJson(json);
+ factory RoleModel.fromJson(Map<String, dynamic> json) {
+   final map = Map<String, dynamic>.from(json);
+   // Backward compat: accept legacy `authorities` as `permissions`.
+   if (!map.containsKey('permissions') && map.containsKey('authorities')) {
+     map['permissions'] = map['authorities'];
+   }
+   return _$RoleModelFromJson(map);
+ }

추가 확인:

  • 서버에서 여전히 authorities를 요구한다면, API 호출 직전 permissions→authorities 변환 어댑터 또는 별도 직렬화용 DTO 분리 고려.
lib/app/modules/groups/domain/entities/authority_entity.dart (1)

2-5: 권한 표현의 타입 안정성 개선 제안

List<String> 대신 enum Permission(+ 확장 메서드) 또는 값 객체로 전환하면 오타/불일치 리스크를 줄일 수 있습니다. 마이그레이션이 크면 최소한 상수 집합을 한 곳에 정의해 재사용을 유도해 주세요.

assets/i18n/strings_ru.i18n.json (1)

110-115: 역할 라벨 표기 통일 및 어휘 개선 제안

  • ‘president’만 소문자(президент)이고 나머지는 대문자 시작입니다. 대문자로 통일 권장.
  • ‘member’의 러시아어 번역 ‘член’은 맥락상 어색할 수 있어 ‘Участник’를 권장합니다.
       "role": "Роль",
-      "president": "президент",
+      "president": "Президент",
       "admin": "Администратор",
       "manager": "Менеджер",
-      "member": "член"
+      "member": "Участник"
assets/i18n/strings_jp.i18n.json (1)

88-93: JP: “Notion/ノーション” 표기 일관성

본 파일 내 다른 위치에서 “Notion”을 사용하고 있어 해당 섹션도 “Notion”으로 통일을 권장합니다.

-      "header": "ノーションページリンクを更新",
-      "hintText": "ノーションリンクを入力してください",
+      "header": "Notionページリンクを更新",
+      "hintText": "Notionリンクを入力してください",
       "loading": "Notionを読み込み中...",
-      "error": "ノーションのリンクの読み込みに失敗しました",
-      "empty": "ノーションリンクを入力してください"
+      "error": "Notionリンクの読み込みに失敗しました",
+      "empty": "Notionリンクを入力してください"
assets/i18n/strings_ko.i18n.json (1)

116-118: 문구 톤 개선 제안(‘당신’ 지양)

한국어 UI에서는 2인칭 ‘당신’보다 1인칭/객관식 표현이 자연스럽습니다.

-      "changePresidentDescription": "당신의 권한은 관리자로 변경됩니다.",
+      "changePresidentDescription": "내 역할은 관리자로 변경됩니다."
assets/i18n/strings.i18n.json (3)

37-39: EN: 예시값 언어 혼용 정리

영문 리소스의 예시값에 한글이 포함되어 있습니다. 순수 영문 예시로 정리 권장.

-          "hint": "인포팀 (Infoteam)",
+          "hint": "Infoteam",

91-91: EN: 에러 문구 문법 수정

더 자연스러운 표현으로 제안드립니다.

-        "error": "Failed Loading Notion Link",
+        "error": "Failed to load Notion link",

116-117: EN: 단수 리더십 직함과 개념 일치

  • 유일 리더이므로 “a president” → “the president”
  • 권한(permissions)보다 역할(role)을 Admin으로 변경한다는 표현이 자연스럽습니다.
-      "changePresidentTitle": "Would you like to make $name a president?",
-      "changePresidentDescription": "Your permissions will be changed to Admin.",
+      "changePresidentTitle": "Would you like to make $name the president?",
+      "changePresidentDescription": "Your role will be changed to Admin.",
lib/app/modules/groups/data/repositories/rest_group_repository.dart (1)

134-138: 대통령 변경 후 리스트 스테이트 동기화 필요

다른 멤버십 변동 API(deleteGroup/removeMember/modifyGroup 등)와 달리, 대통령 변경 후 _refreshGroups()가 호출되지 않아 UI가 최신 상태로 갱신되지 않을 수 있습니다. 저장소 레벨에서 일관되게 리프레시하는 편이 안전합니다.

다음과 같이 비동기 처리 및 리프레시를 추가해 주세요:

   @override
-  Future<void> changePresident(
-      {required String uuid, required String targetUuid}) {
-    return _api.updatePresident(uuid, UpdatePresidentModel(targetUuid));
-  }
+  Future<void> changePresident({
+    required String uuid,
+    required String targetUuid,
+  }) async {
+    await _api.updatePresident(uuid, UpdatePresidentModel(targetUuid));
+    await _refreshGroups();
+  }

또한, 이 작업 이후에 멤버 목록을 사용하는 화면이 있다면(예: 관리 페이지), Bloc에서 별도의 멤버 재조회 트리거가 필요한지 확인 부탁드립니다.

lib/app/modules/groups/data/enums/group_member_role.dart (1)

9-11: 서버 역할 ID 매핑과 권한 정책 확인 필요

president 추가와 toInt()에서 0 매핑은 합리적으로 보이나, 서버의 roleId 정의와 반드시 일치해야 합니다. 특히:

  • 초대 링크 생성 시 roleId0(president) 발급이 허용되는지 정책 확인 필요
  • 기존 admin=1, manager=2, member=3 순서가 서버와 동일한지 검증 필요

서버/모바일 간 상수 불일치 시 권한 오부여 위험이 큽니다.

필요 시 enum에 서버 ID를 직접 보관하는 방식으로 가독성과 안정성을 높일 수 있습니다(예: final int id; const president(0), int toInt()=>id).

Also applies to: 21-32

lib/app/modules/groups/data/data_sources/remote/group_api.dart (1)

105-109: 요청 바디 타입 전환 적절 + 파라미터 명칭 정리 제안

Map<String,String>UpdatePresidentModel 전환은 타입 안정성에 이점이 큽니다. 다만 파라미터명이 모델을 나타내지 않아 약간 혼동될 수 있습니다. 다음처럼 이름을 명확히 해주세요.

   @PATCH('{uuid}/president')
   Future<void> updatePresident(
     @Path('uuid') String uuid,
-    @Body() UpdatePresidentModel newPresidentUuid,
+    @Body() UpdatePresidentModel model,
   );

또한 서버가 { "newPresidentUuid": "<uuid>" } 형태를 기대하는지(스네이크/카멜 케이스 포함) 재확인 부탁드립니다.

lib/app/modules/groups/data/data_sources/models/update_president_model.dart (1)

8-10: 명시적(named) 파라미터로 가독성 및 오용 방지

단일 위치 파라미터는 호출처에서 의미가 불분명합니다. named 파라미터로 전환을 권장합니다.

-  factory UpdatePresidentModel(
-    String newPresidentUuid,
-  ) = _UpdatePresidentModel;
+  factory UpdatePresidentModel({
+    required String newPresidentUuid,
+  }) = _UpdatePresidentModel;

호출부는 UpdatePresidentModel(newPresidentUuid: targetUuid)로 업데이트 필요합니다.

lib/app/modules/groups/presentation/widgets/group_member_card.dart (4)

14-16: edit 전용 식별자 전달은 OK이나, 불필요 노출 최소화 권장

uuid/myUuid/presidentUuid가 edit 모드에서만 사용됩니다. 현재 구현은 적절하나, 뷰 모드에선 해당 값이 필요 없습니다. 필드 노출 최소화를 위해 다음 중 하나를 고려해 주세요.

  • 필드를 nullable로 두고 editMode 생성자에서만 required 처리(+ assert)
  • 또는 viewMode에서 내부 기본값을 빈 문자열로 통일(아래 니트픽과 연계)

Also applies to: 26-29


38-41: 매직 넘버 문자열 제거

뷰 모드 기본값 "3141592", "1618033", "2718281"은 의미가 불명확합니다. 빈 문자열로 대체해 혼동을 줄이는 편이 낫습니다.

   })  : editMode = false,
         onBanish = null,
-        onChanged = null,
-        uuid = "3141592",
-        myUuid = "1618033",
-        presidentUuid = "2718281";
+        onChanged = null,
+        uuid = '',
+        myUuid = '',
+        presidentUuid = '';

110-151: 대통령 행 UI 고정 처리 합리적 + 역할 드롭다운 동작 검증 필요

  • 대통령 행을 배지로 고정하고, 그 외 행에만 셀렉트를 제공하는 UX는 명확합니다.
  • 단, 셀렉트에서 GroupMemberRole.president를 선택했을 때 Bloc에서 grantUserRole이 아닌 changePresident 플로우를 타도록 매핑되어 있는지 확인이 필요합니다(서버가 대통령 변경을 전용 엔드포인트로 처리).

옵션: 빌드 시마다 리스트를 생성하는 비용을 줄이려면 entries를 지역 변수로 한 번 계산해 재사용해도 됩니다(미세 최적화).


153-158: 추방 버튼 가드 강화 제안

대통령은 숨기고 있으나, 사용자가 자기 자신을 추방하는 시나리오는 의도되지 않을 수 있습니다. uuid == myUuid인 경우도 버튼을 숨기거나 별도 ‘탈퇴’ 플로우를 안내하는 편이 안전합니다.

- if (uuid != presidentUuid)
+ if (uuid != presidentUuid && uuid != myUuid)
   ZiggleButton.small(
     onPressed: onBanish,
     child: Text(context.t.group.memberCard.banish),
   )
lib/app/modules/groups/presentation/blocs/group_member_bloc.dart (1)

91-93: Loaded 상태에서 presidentUuid를 non-null로 강제하는 계약 확인 필요

도메인상 항상 대통령이 존재한다는 보장이 없다면 String?로 바꾸거나, 위 핸들러에서 에러/대체 처리를 명확히 해야 합니다. 현재는 런타임 크래시 위험이 있습니다.

원하시면 Freezed 시그니처 변경과 핸들러 보강까지 포함한 패치 제안 드리겠습니다.

lib/app/modules/groups/presentation/pages/group_management_member_page.dart (5)

27-33: 초기 로드 이벤트가 중복 디스패치됩니다

create에서 한 번, Builder에서 또 한 번 getMembers를 보냅니다. API 중복 호출/깜빡임 원인입니다.

   return BlocProvider<GroupMemberBloc>(
     lazy: false,
     create: (context) =>
         sl<GroupMemberBloc>()..add(GroupMemberEvent.getMembers(uuid)),
-    child: Builder(builder: (context) {
-      context.read<GroupMemberBloc>().add(GroupMemberEvent.getMembers(uuid));
-      return Scaffold(
+    child: Builder(builder: (context) {
+      return Scaffold(

대안: 위 diff 대신 lazy: true로 두고 Builder에서 한 번만 디스패치하는 방식도 가능합니다. 일관되게 하나만 남겨주세요.


61-85: 역할 변경 onChanged: null/동일값 가드 및 불필요 호출 방지

e! 강제 언래핑은 NPE 위험이 있고, 동일 역할 선택 시에도 네트워크 호출을 보냅니다.

-                              onChanged: (e) {
+                              onChanged: (e) {
+                                if (e == null || e == members[index].role) {
+                                  return;
+                                }
                                 if (e == GroupMemberRole.president) {
                                   context.showDialog<bool>(
                                     title: context.t.group.memberCard
                                         .changePresidentTitle(
                                             name: members[index].name),
                                     content: context.t.group.memberCard
                                         .changePresidentDescription,
                                     onConfirm: (dialogContext) {
                                       context
                                           .read<GroupMemberBloc>()
                                           .add(GroupMemberEvent.changePresident(
                                             uuid,
                                             members[index].uuid,
                                           ));
                                       Navigator.of(dialogContext).pop();
                                     },
                                   );
                                 } else {
                                   context.read<GroupMemberBloc>().add(
                                       GroupMemberEvent.grantRoleToUser(
                                           uuid,
                                           members[index].uuid,
-                                          e!,
+                                          e,
                                           members[index].role!));
                                 }
                               },

87-98: 추방 로직 Bloc 불일치 및 프로바이더 유무 확인 필요

이 페이지에서는 GroupMemberBloc을 제공하지만, 추방은 GroupManagementBloc에 디스패치합니다. 상위에 GroupManagementBloc 프로바이더가 없으면 런타임 에러가 납니다. 또한 멤버 화면의 나머지 액션은 GroupMemberBloc을 사용합니다. 한 Bloc으로 통일을 권장합니다.

대안 예시(멤버 Bloc 통일 + 자동 새로고침은 Bloc 쪽에서 보강 권장):

-                                    context.read<GroupManagementBloc>().add(
-                                        GroupManagementEvent.removeMember(
-                                            uuid, members[index].uuid));
+                                    context.read<GroupMemberBloc>().add(
+                                        GroupMemberEvent.removeMember(
+                                            uuid, members[index].uuid));

58-60: email/role의 강제 언래핑(!) 안전성 확인

email!, role!이 항상 보장되지 않으면 크래시합니다. 도메인 계약을 재확인하거나, 기본값/플레이스홀더로 방어 코드를 넣어주세요.


43-50: 에러/초기 상태 UI가 빈 컨테이너로 끝남

orElse: () => Container()로 초기/에러 상태에서 공백 화면이 보입니다. 최소한 에러 메시지와 재시도 버튼을 제공해 주세요.

-                return state.maybeWhen(
-                  orElse: () => Container(),
+                return state.maybeWhen(
+                  orElse: () => Center(child: Text(context.t.common.empty)),
                   loading: () => Center(
                     child: Lottie.asset(Assets.lotties.loading,
                         height: MediaQuery.of(context).size.width * 0.2,
                         width: MediaQuery.of(context).size.width * 0.2),
                   ),
+                  error: (msg) => Center(
+                    child: Column(
+                      mainAxisSize: MainAxisSize.min,
+                      children: [
+                        Text(msg, textAlign: TextAlign.center),
+                        const SizedBox(height: 12),
+                        ZiggleButton.outlined(
+                          child: Text(context.t.common.retry),
+                          onPressed: () => context
+                              .read<GroupMemberBloc>()
+                              .add(GroupMemberEvent.getMembers(uuid)),
+                        ),
+                      ],
+                    ),
+                  ),

Also applies to: 116-118

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc143b3 and 1aea885.

📒 Files selected for processing (17)
  • assets/i18n/strings.i18n.json (3 hunks)
  • assets/i18n/strings_jp.i18n.json (3 hunks)
  • assets/i18n/strings_ko.i18n.json (3 hunks)
  • assets/i18n/strings_ru.i18n.json (3 hunks)
  • lib/app/modules/groups/data/data_sources/models/create_role_model.dart (1 hunks)
  • lib/app/modules/groups/data/data_sources/models/role_model.dart (1 hunks)
  • lib/app/modules/groups/data/data_sources/models/update_president_model.dart (1 hunks)
  • lib/app/modules/groups/data/data_sources/models/update_role_model.dart (1 hunks)
  • lib/app/modules/groups/data/data_sources/remote/group_api.dart (2 hunks)
  • lib/app/modules/groups/data/enums/group_member_role.dart (4 hunks)
  • lib/app/modules/groups/data/repositories/rest_group_repository.dart (2 hunks)
  • lib/app/modules/groups/domain/entities/authority_entity.dart (1 hunks)
  • lib/app/modules/groups/domain/entities/role_entity.dart (1 hunks)
  • lib/app/modules/groups/domain/repository/group_repository.dart (1 hunks)
  • lib/app/modules/groups/presentation/blocs/group_member_bloc.dart (3 hunks)
  • lib/app/modules/groups/presentation/pages/group_management_member_page.dart (2 hunks)
  • lib/app/modules/groups/presentation/widgets/group_member_card.dart (4 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2024-10-13T16:03:27.218Z
Learnt from: dawnfire05
PR: gsainfoteam/ziggle-flutter#469
File: assets/i18n/group.i18n.json:47-54
Timestamp: 2024-10-13T16:03:27.218Z
Learning: `group_member_card.dart` 파일에서 `memberCard` 번역이 사용되고 있습니다.

Applied to files:

  • assets/i18n/strings_ko.i18n.json
  • lib/app/modules/groups/presentation/widgets/group_member_card.dart
📚 Learning: 2024-11-08T18:23:32.322Z
Learnt from: dawnfire05
PR: gsainfoteam/ziggle-flutter#424
File: lib/app/modules/user/data/repositories/ziggle_rest_auth_repository.dart:10-12
Timestamp: 2024-11-08T18:23:32.322Z
Learning: Flutter 프로젝트의 `lib/app/modules/user/data/repositories` 경로에서 의존성 주입 시, `ZiggleRestAuthRepository`와 `GroupsRestAuthRepository`를 구분하기 위해서는 `named` 어노테이션을 사용해야 합니다.

Applied to files:

  • lib/app/modules/groups/presentation/blocs/group_member_bloc.dart
  • lib/app/modules/groups/presentation/pages/group_management_member_page.dart
📚 Learning: 2024-10-02T05:45:07.129Z
Learnt from: 2paperstar
PR: gsainfoteam/ziggle-flutter#423
File: lib/app/modules/notices/presentation/pages/notice_edit_page.dart:10-10
Timestamp: 2024-10-02T05:45:07.129Z
Learning: 'lib/app/modules/notices/presentation/pages/notice_edit_page.dart' 파일에서 'package:ziggle/app/modules/core/domain/enums/page_source.dart' import는 L60에서 사용되고 있습니다.

Applied to files:

  • lib/app/modules/groups/presentation/pages/group_management_member_page.dart
📚 Learning: 2024-10-03T16:19:08.881Z
Learnt from: 2paperstar
PR: gsainfoteam/ziggle-flutter#423
File: lib/app/modules/user/presentation/pages/packages_page.dart:22-22
Timestamp: 2024-10-03T16:19:08.881Z
Learning: `PageSource.unknown`의 사용은 다음 파일들에서 의도된 것입니다:
- `lib/app/modules/user/presentation/pages/packages_page.dart`
- `lib/app/modules/groups/presentation/pages/group_management_main_page.dart`
- `lib/app/modules/groups/presentation/layouts/group_creation_layout.dart`
- `lib/app/modules/common/presentation/widgets/ziggle_back_button.dart`

Applied to files:

  • lib/app/modules/groups/presentation/pages/group_management_member_page.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Android App and Test
  • GitHub Check: Build iOS App and Test
  • GitHub Check: GitGuardian Security Checks
🔇 Additional comments (14)
lib/app/modules/groups/domain/entities/role_entity.dart (1)

7-14: repository 인터페이스 및 구현의 AuthorityEntity→PermissionEntity 변경 및 파라미터명 수정

  • lib/app/modules/groups/domain/repository/group_repository.dart (59): AuthorityEntity authorityPermissionEntity permission
  • lib/app/modules/groups/data/repositories/rest_group_repository.dart (159): 동일 적용
    JSON 직렬화 키(authoritiespermissions) 점검
⛔ Skipped due to learnings
Learnt from: dawnfire05
PR: gsainfoteam/ziggle-flutter#424
File: lib/app/modules/user/data/repositories/ziggle_rest_auth_repository.dart:10-12
Timestamp: 2024-11-08T18:23:32.322Z
Learning: Flutter 프로젝트의 `lib/app/modules/user/data/repositories` 경로에서 의존성 주입 시, `ZiggleRestAuthRepository`와 `GroupsRestAuthRepository`를 구분하기 위해서는 `named` 어노테이션을 사용해야 합니다.
lib/app/modules/groups/data/data_sources/models/create_role_model.dart (1)

8-14: CreateRoleModel 직렬화 키 확인 필요(서버 기대치: permissions vs authorities)

신규/구 서버가 서로 다른 키를 기대할 수 있습니다. 서버가 아직 authorities를 받는다면 아래처럼 필드에 JsonKey를 지정해 송신 키만 구버전과 맞추고, 읽기는 사용하지 않거나 필요 시 정규화를 추가해 주세요.

옵션 A(서버가 authorities 기대):

-    required List<String> permissions,
+    @JsonKey(name: 'authorities') required List<String> permissions,

옵션 B(서버가 permissions로 이미 전환됨): 현 상태 유지. 이 경우 데이터 계층 전체(생성/수정/조회 DTO)의 키 일관성도 함께 재검증해 주세요.

assets/i18n/strings_ru.i18n.json (2)

37-39: RU: 새 키 추가 LGTM

힌트/중복 이름 오류 메시지 자연스럽습니다.


91-93: RU: 노션 링크 검증 메시지 추가 LGTM

에러/빈 값 메시지 추가 적절합니다.

lib/app/modules/groups/data/data_sources/models/update_role_model.dart (1)

8-10: 네이밍 파라미터 전환 권장
UpdateRoleModel 생성자를 위치 인자 대신 네이밍 파라미터로 전환해 주세요.

- factory UpdateRoleModel(
-   List<String> permissions,
- ) = _UpdateRoleModel;
+ factory UpdateRoleModel({
+   required List<String> permissions,
+ }) = _UpdateRoleModel;
  • 호출부가 모두 변경된 시그니처(permissions:)로 업데이트되었는지 확인 필요 (스크립트에서 검색 결과 없음).
  • JSON 키 "permissions"가 서버 기대 스펙과 일치하는지 점검 필요 (스크립트에서 검색 결과 없음).
assets/i18n/strings_ko.i18n.json (2)

37-39: KO: 새 키 추가 LGTM

힌트/중복 이름 오류 메시지 자연스럽습니다.


91-93: KO: 노션 링크 검증 메시지 추가 LGTM

에러/빈 값 메시지 추가 적절합니다.

lib/app/modules/groups/data/repositories/rest_group_repository.dart (1)

8-8: UpdatePresidentModel import 추가 적절

API/모델 변경과 일치합니다. 별도 이슈 없습니다.

lib/app/modules/groups/data/enums/group_member_role.dart (2)

34-35: 편의 메서드 추가 LGTM

isPresident() 보일러플레이트 감소에 유용합니다.


46-48: 로컬라이제이션 매핑 추가 적절

context.t.group.memberCard.role.president 매핑이 추가되었습니다. i18n 리소스(EN/JP/KO/RU)가 모두 있는지 CI/i18n 체크로 한 번 더 검증해 주세요.

lib/app/modules/groups/data/data_sources/remote/group_api.dart (1)

16-16: 모델 임포트 LGTM

UpdatePresidentModel 사용 준비가 잘 되어 있습니다.

lib/app/modules/groups/data/data_sources/models/update_president_model.dart (1)

12-14: JSON 키 확인 요청

Freezed 기본 설정이면 키가 newPresidentUuid로 직렬화됩니다. 서버가 다른 네이밍(예: new_president_uuid)을 요구한다면 @JsonKey(name: '...')를 추가해야 합니다. 백엔드 계약을 확인해 주세요.

lib/app/modules/groups/presentation/blocs/group_member_bloc.dart (2)

7-9: DI 네이밍 주입 사용이 적절합니다

@Named.from(GroupsRestAuthRepository)RestAuthRepository 구현을 구분한 점 좋습니다. 장기 학습 노트와도 합치합니다.

Also applies to: 15-15, 17-19


74-85: Freezed 코드 생성물 재생성 필요
로컬 환경에서 flutter pub run build_runner build --delete-conflicting-outputs를 실행하여 변경된 이벤트 시그니처에 맞춰 Freezed 코드 생성물이 업데이트되었는지 확인하세요.

Comment on lines +116 to 118
"changePresidentTitle": "? $name",
"changePresidentDescription": "?",
"banish": "追放",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

[중요] JP: 변경 모달 번역 누락(‘?’)

일본어 번역이 미완입니다. 아래처럼 제안드립니다.

-      "changePresidentTitle": "? $name",
-      "changePresidentDescription": "?",
+      "changePresidentTitle": "$name さんを社長にしますか?",
+      "changePresidentDescription": "あなたの役割は「管理者」に変更されます。"
📝 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
"changePresidentTitle": "? $name",
"changePresidentDescription": "?",
"banish": "追放",
"changePresidentTitle": "$name さんを社長にしますか?",
"changePresidentDescription": "あなたの役割は「管理者」に変更されます。",
"banish": "追放",
🤖 Prompt for AI Agents
In assets/i18n/strings_jp.i18n.json around lines 116 to 118, the keys
"changePresidentTitle" and "changePresidentDescription" contain placeholder '?'
values; replace them with proper Japanese strings: set "changePresidentTitle" to
"議長を $name に交代しますか?" and "changePresidentDescription" to "議長を変更してもよろしいですか?"
(ensure JSON string quoting/escaping is correct).

Comment on lines +116 to 118
"changePresidentTitle": "? $name",
"changePresidentDescription": "?",
"banish": "Исключить",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

[중요] 변경 모달 러시아어 번역 누락(‘?’)

사용자 노출 문자열이 ‘?’로 남아있습니다. 아래처럼 자연스러운 문구로 교체해주세요.

-      "changePresidentTitle": "? $name",
-      "changePresidentDescription": "?",
+      "changePresidentTitle": "Назначить $name президентом группы?",
+      "changePresidentDescription": "Ваша роль будет изменена на «Администратор»."
📝 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
"changePresidentTitle": "? $name",
"changePresidentDescription": "?",
"banish": "Исключить",
"changePresidentTitle": "Назначить $name президентом группы?",
"changePresidentDescription": "Ваша роль будет изменена на «Администратор».",
"banish": "Исключить",
🤖 Prompt for AI Agents
assets/i18n/strings_ru.i18n.json around lines 116-118: the strings for
changePresidentTitle and changePresidentDescription are left as '?' and must be
replaced with natural Russian phrases; set changePresidentTitle to a concise
title like "Сменить президента: $name" and changePresidentDescription to a clear
confirmation like "Вы уверены, что хотите назначить $name президентом?" (keep
placeholders intact as $name).

Comment on lines 23 to 30
on<_GetMembers>((event, emit) async {
emit(GroupMemberState.loading());
final members = await _repository.getMembers(event.uuid);
emit(GroupMemberState.loaded(members.list));
final group = await _repository.getGroup(event.uuid);
final myUuid = await _authRepository.info().then((u) => u.uuid);
emit(
GroupMemberState.loaded(members.list, myUuid, group.president!.uuid));
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

예외 처리 누락 + null 강제 연산자 때문에 런타임 크래시 위험

  • group.president!.uuid에서 president가 null이면 바로 크래시합니다.
  • API 실패 시 try/catch가 없어 Bloc 핸들러가 예외로 종료될 수 있습니다.
  • 3개의 I/O를 직렬로 호출하고 있어 지연이 불필요하게 길어집니다.

아래처럼 동시 실행 + 견고한 예외 처리 + null 가드로 보완해 주세요.

     on<_GetMembers>((event, emit) async {
-      emit(GroupMemberState.loading());
-      final members = await _repository.getMembers(event.uuid);
-      final group = await _repository.getGroup(event.uuid);
-      final myUuid = await _authRepository.info().then((u) => u.uuid);
-      emit(
-          GroupMemberState.loaded(members.list, myUuid, group.president!.uuid));
+      emit(GroupMemberState.loading());
+      try {
+        final membersF = _repository.getMembers(event.uuid);
+        final groupF = _repository.getGroup(event.uuid);
+        final meF = _authRepository.info();
+
+        final members = await membersF;
+        final group = await groupF;
+        final myUuid = (await meF).uuid;
+
+        final presidentUuid = group.president?.uuid;
+        if (presidentUuid == null) {
+          emit(const GroupMemberState.error('Group has no president'));
+          return;
+        }
+        emit(GroupMemberState.loaded(members.list, myUuid, presidentUuid));
+      } on Exception catch (e) {
+        emit(GroupMemberState.error(e.toString()));
+      }
     });
📝 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
on<_GetMembers>((event, emit) async {
emit(GroupMemberState.loading());
final members = await _repository.getMembers(event.uuid);
emit(GroupMemberState.loaded(members.list));
final group = await _repository.getGroup(event.uuid);
final myUuid = await _authRepository.info().then((u) => u.uuid);
emit(
GroupMemberState.loaded(members.list, myUuid, group.president!.uuid));
});
on<_GetMembers>((event, emit) async {
emit(GroupMemberState.loading());
try {
final membersF = _repository.getMembers(event.uuid);
final groupF = _repository.getGroup(event.uuid);
final meF = _authRepository.info();
final members = await membersF;
final group = await groupF;
final myUuid = (await meF).uuid;
final presidentUuid = group.president?.uuid;
if (presidentUuid == null) {
emit(const GroupMemberState.error('Group has no president'));
return;
}
emit(GroupMemberState.loaded(members.list, myUuid, presidentUuid));
} on Exception catch (e) {
emit(GroupMemberState.error(e.toString()));
}
});
🤖 Prompt for AI Agents
In lib/app/modules/groups/presentation/blocs/group_member_bloc.dart around lines
23 to 30, the handler lacks error handling, uses a null-forcing operator on
group.president which can crash, and performs three I/O calls serially causing
unnecessary latency; wrap the async block in try/catch and emit an error state
on failure, run getMembers, getGroup and authRepository.info concurrently (e.g.
Future.wait) to reduce latency, and guard against a null president by deriving
presidentUuid = group.presid ent?.uuid ?? '' (or emit a specific error/empty
value) before emitting GroupMemberState.loaded.

Comment on lines +58 to +70
on<_ChangePresident>((event, emit) async {
emit(GroupMemberState.loading());
try {
await _repository.changePresident(
uuid: event.uuid,
targetUuid: event.targetUuid,
);
} on Exception catch (e) {
emit(_Error(e.toString()));
}
emit(GroupMemberState.success());
add(GroupMemberEvent.getMembers(event.uuid));
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

changePresident: 에러 후에도 success 상태를 내보내는 동일한 버그

실패 시에도 성공 상태를 방출합니다. 위 핸들러와 동일 패턴으로 수정 필요합니다.

     on<_ChangePresident>((event, emit) async {
       emit(GroupMemberState.loading());
       try {
         await _repository.changePresident(
           uuid: event.uuid,
           targetUuid: event.targetUuid,
         );
-      } on Exception catch (e) {
-        emit(_Error(e.toString()));
-      }
-      emit(GroupMemberState.success());
-      add(GroupMemberEvent.getMembers(event.uuid));
+        emit(GroupMemberState.success());
+        add(GroupMemberEvent.getMembers(event.uuid));
+      } on Exception catch (e) {
+        emit(GroupMemberState.error(e.toString()));
+      }
     });
📝 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
on<_ChangePresident>((event, emit) async {
emit(GroupMemberState.loading());
try {
await _repository.changePresident(
uuid: event.uuid,
targetUuid: event.targetUuid,
);
} on Exception catch (e) {
emit(_Error(e.toString()));
}
emit(GroupMemberState.success());
add(GroupMemberEvent.getMembers(event.uuid));
});
on<_ChangePresident>((event, emit) async {
emit(GroupMemberState.loading());
try {
await _repository.changePresident(
uuid: event.uuid,
targetUuid: event.targetUuid,
);
emit(GroupMemberState.success());
add(GroupMemberEvent.getMembers(event.uuid));
} on Exception catch (e) {
emit(GroupMemberState.error(e.toString()));
}
});
🤖 Prompt for AI Agents
In lib/app/modules/groups/presentation/blocs/group_member_bloc.dart around lines
58 to 70, the changePresident handler emits a success state and triggers
getMembers even when an exception occurs; move the
emit(GroupMemberState.success()) and add(GroupMemberEvent.getMembers(...)) into
the try block after the await so they only run on success, and ensure the catch
block only emits the error (or returns) so execution doesn't continue to the
success path.

);
},
child: Builder(builder: (context) {
context.read<GroupMemberBloc>().add(GroupMemberEvent.getMembers(uuid));
Copy link
Member

Choose a reason for hiding this comment

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

Provider에서 getMembers 이벤트 보내서 Builder에서 또 보낼 필요는 없을 것 같아요

@2paperstar
Copy link
Member

changePresident.webm

셀프 추방이 돼요..?

Comment on lines +14 to +16
final String uuid;
final String myUuid;
final String presidentUuid;
Copy link
Member

Choose a reason for hiding this comment

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

좀 짜치는데 MemberEntity를 그대로 받으면 안 되나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

그 부분이 좀 더 가독성 있어보이네요. 수정하겠슴다

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.

feat: give president to other people

3 participants