-
Notifications
You must be signed in to change notification settings - Fork 9
feat: give president to other people #616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
feat: give president to other people #616
Conversation
📝 WalkthroughWalkthrough그룹 관리 기능에 대통령(president) 역할과 회장 변경 플로우가 추가되었고, 권한 필드 명칭이 authorities에서 permissions로 일괄 변경되었습니다. i18n 문자열이 다국어(EN/JP/KO/RU)로 보강되었으며, Bloc/페이지/위젯/UI, 도메인/데이터 모델, API/리포지토리 계층이 이에 맞춰 수정·추가되었습니다. Changes
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)
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 재조회
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewersPoem
✨ Finishing Touches🧪 Generate unit tests
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. Comment |
There was a problem hiding this 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 이후에도
success와getMembers를 실행합니다. 실패가 성공으로 보이는 치명적 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에 legacyauthorities키 호환 처리 추가
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 정의와 반드시 일치해야 합니다. 특히:
- 초대 링크 생성 시
roleId로0(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
📒 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.jsonlib/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.dartlib/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 authority→PermissionEntity permission- lib/app/modules/groups/data/repositories/rest_group_repository.dart (159): 동일 적용
JSON 직렬화 키(authorities→permissions) 점검⛔ 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 코드 생성물이 업데이트되었는지 확인하세요.
| "changePresidentTitle": "? $name", | ||
| "changePresidentDescription": "?", | ||
| "banish": "追放", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[중요] 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.
| "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).
| "changePresidentTitle": "? $name", | ||
| "changePresidentDescription": "?", | ||
| "banish": "Исключить", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[중요] 변경 모달 러시아어 번역 누락(‘?’)
사용자 노출 문자열이 ‘?’로 남아있습니다. 아래처럼 자연스러운 문구로 교체해주세요.
- "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.
| "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).
| 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)); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
예외 처리 누락 + 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.
| 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.
| 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)); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
lib/app/modules/groups/presentation/pages/group_management_member_page.dart
Show resolved
Hide resolved
| ); | ||
| }, | ||
| child: Builder(builder: (context) { | ||
| context.read<GroupMemberBloc>().add(GroupMemberEvent.getMembers(uuid)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provider에서 getMembers 이벤트 보내서 Builder에서 또 보낼 필요는 없을 것 같아요
셀프 추방이 돼요..? |
| final String uuid; | ||
| final String myUuid; | ||
| final String presidentUuid; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좀 짜치는데 MemberEntity를 그대로 받으면 안 되나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그 부분이 좀 더 가독성 있어보이네요. 수정하겠슴다
changePresident.webm
Summary by CodeRabbit
신기능
개선
스타일/UX