Add agent-ops rules and documentation files
This commit is contained in:
parent
d36154a3c3
commit
73375477ba
13 changed files with 845 additions and 0 deletions
9
CLAUDE.md
Normal file
9
CLAUDE.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
## 응답 언어
|
||||||
|
한국어로 응답한다.
|
||||||
|
|
||||||
|
## 기본 원칙
|
||||||
|
- 기존 구조를 우선한다.
|
||||||
|
- 새 파일 생성보다 기존 파일 수정을 우선한다.
|
||||||
|
- 작업 전 `agent-ops/skills/router.md`를 참조하여 필요한 skill 또는 domain rule만 로드한다.
|
||||||
|
- 관련 rule / skill만 최소로 로드한다.
|
||||||
|
- 세부 규칙은 `agent-ops/` 아래 파일을 따른다.
|
||||||
9
GEMINI.md
Normal file
9
GEMINI.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
## 응답 언어
|
||||||
|
한국어로 응답한다.
|
||||||
|
|
||||||
|
## 기본 원칙
|
||||||
|
- 기존 구조를 우선한다.
|
||||||
|
- 새 파일 생성보다 기존 파일 수정을 우선한다.
|
||||||
|
- 작업 전 `agent-ops/skills/router.md`를 참조하여 필요한 skill 또는 domain rule만 로드한다.
|
||||||
|
- 관련 rule / skill만 최소로 로드한다.
|
||||||
|
- 세부 규칙은 `agent-ops/` 아래 파일을 따른다.
|
||||||
28
agent-ops/rules/_templates/domain-rule-template.md
Normal file
28
agent-ops/rules/_templates/domain-rule-template.md
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
---
|
||||||
|
name: <domain-name>
|
||||||
|
description: <도메인 한 줄 설명>
|
||||||
|
type: <core | supporting | generic>
|
||||||
|
---
|
||||||
|
|
||||||
|
# <Domain Name>
|
||||||
|
|
||||||
|
## 목적 / 책임
|
||||||
|
- 이 도메인이 담당하는 핵심 책임
|
||||||
|
|
||||||
|
## 포함 경로
|
||||||
|
- `path/to/included/`
|
||||||
|
|
||||||
|
## 제외 경로
|
||||||
|
- `path/to/excluded/`
|
||||||
|
|
||||||
|
## 주요 구성 요소
|
||||||
|
- `ComponentName`: 역할 설명
|
||||||
|
|
||||||
|
## 유지할 패턴
|
||||||
|
- 기존 코드에서 반드시 유지해야 할 패턴
|
||||||
|
|
||||||
|
## 다른 도메인과의 경계
|
||||||
|
- **<other-domain>**: 이 도메인은 ~하고, <other-domain>은 ~한다.
|
||||||
|
|
||||||
|
## 금지 사항
|
||||||
|
- 이 도메인에서 하면 안 되는 것
|
||||||
43
agent-ops/rules/domain/android-native/rules.md
Normal file
43
agent-ops/rules/domain/android-native/rules.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
name: android-native
|
||||||
|
description: Android 앱 진입점, 채널 등록, 앱 생명주기 관리 (Supporting Domain)
|
||||||
|
type: supporting
|
||||||
|
---
|
||||||
|
|
||||||
|
# Android Native
|
||||||
|
|
||||||
|
## 목적 / 책임
|
||||||
|
- Android 앱 진입점 및 초기화
|
||||||
|
- Flutter ↔ Android 채널(EventChannel, MethodChannel) 등록
|
||||||
|
- 알림 채널(NotificationChannel) 생성 및 관리
|
||||||
|
- 앱 생명주기(포그라운드/백그라운드) 상태 추적
|
||||||
|
|
||||||
|
## 포함 경로
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/MainActivity.kt`
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/MainApplication.kt`
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/AppLifecycleTracker.kt`
|
||||||
|
- `android/app/src/main/AndroidManifest.xml`
|
||||||
|
- `android/app/build.gradle.kts`
|
||||||
|
|
||||||
|
## 제외 경로
|
||||||
|
- `android/.../db/` (database 도메인)
|
||||||
|
- `android/.../helpers/` (각 담당 도메인)
|
||||||
|
- `android/.../MattermostFirebaseMessagingService.kt` (push-notification 도메인)
|
||||||
|
|
||||||
|
## 주요 구성 요소
|
||||||
|
- `MainActivity`: EventChannel & MethodChannel 등록
|
||||||
|
- `MainApplication`: 앱 시작 시 알림 채널 생성, AppLifecycleTracker 등록
|
||||||
|
- `AppLifecycleTracker`: 앱 포그라운드/백그라운드 상태 추적 (ActivityLifecycleCallbacks)
|
||||||
|
|
||||||
|
## 유지할 패턴
|
||||||
|
- 알림 채널은 `MainApplication.onCreate()`에서 생성
|
||||||
|
- EventChannel ID: `com.tokilabs.mattermost/events`
|
||||||
|
- MethodChannel ID: `com.tokilabs.mattermost/methods` (또는 실제 ID 확인 후 갱신)
|
||||||
|
|
||||||
|
## 다른 도메인과의 경계
|
||||||
|
- **push-notification**: 알림 채널 생성은 android-native, 실제 알림 표시는 push-notification
|
||||||
|
- **flutter-app**: Flutter 레이어와의 통신은 채널을 통해서만, 직접 Flutter 코드 수정 지양
|
||||||
|
|
||||||
|
## 금지 사항
|
||||||
|
- 비즈니스 로직을 MainActivity/MainApplication에 넣지 않는다.
|
||||||
|
- 채널 ID를 하드코딩할 경우 반드시 상수로 추출한다.
|
||||||
48
agent-ops/rules/domain/database/rules.md
Normal file
48
agent-ops/rules/domain/database/rules.md
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
---
|
||||||
|
name: database
|
||||||
|
description: Room DB 기반 로컬 데이터 저장 - 서버/사용자/디바이스 토큰 관리 (Supporting Domain)
|
||||||
|
type: supporting
|
||||||
|
---
|
||||||
|
|
||||||
|
# Database
|
||||||
|
|
||||||
|
## 목적 / 책임
|
||||||
|
- Room Database를 통한 로컬 데이터 저장
|
||||||
|
- Global DB: 디바이스 토큰, 서버 목록 관리
|
||||||
|
- Server DB: 서버별 Config, User, System 데이터 관리
|
||||||
|
- DAO 쿼리 구현 및 엔티티 정의
|
||||||
|
|
||||||
|
## 포함 경로
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/db/GlobalDatabase.kt`
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/db/MattermostDatabase.kt`
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/db/GlobalEntities.kt`
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/db/ServerEntities.kt`
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/helpers/DatabaseHelper.kt`
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/helpers/PushNotificationDataHelper.kt`
|
||||||
|
|
||||||
|
## 제외 경로
|
||||||
|
- DB를 사용하는 다른 도메인 파일 (MattermostFirebaseMessagingService 등)
|
||||||
|
|
||||||
|
## 주요 구성 요소
|
||||||
|
- `GlobalDatabase`: 디바이스 토큰, 서버 목록 (앱 전역)
|
||||||
|
- `MattermostDatabase`: 서버별 DB (Config, User, System 테이블)
|
||||||
|
- `GlobalEntities`: Global DB 엔티티 + DAO 인터페이스
|
||||||
|
- `ServerEntities`: Server DB 엔티티 + DAO 인터페이스
|
||||||
|
- `DatabaseHelper`: DB 인스턴스 접근 추상화
|
||||||
|
- `PushNotificationDataHelper`: **🔴 현재 STUB** - DAO 쿼리 구현 필요 (TODO-1)
|
||||||
|
|
||||||
|
## 유지할 패턴
|
||||||
|
- GlobalDatabase / MattermostDatabase 분리 구조 유지
|
||||||
|
- DatabaseHelper를 통한 간접 접근 (직접 Room 인스턴스 접근 지양)
|
||||||
|
- 서버별 DB는 서버 URL을 키로 구분
|
||||||
|
|
||||||
|
## TODO (push-notification-todo.md 기준)
|
||||||
|
- **[TODO-1] 🔴**: `PushNotificationDataHelper.kt` Room DAO 쿼리 구현
|
||||||
|
|
||||||
|
## 다른 도메인과의 경계
|
||||||
|
- **push-notification**: 알림 데이터 조회 요청은 push-notification이 하고, 실제 쿼리는 이 도메인
|
||||||
|
- **network**: DB와 네트워크는 분리, 캐시 전략은 각 도메인이 독립 관리
|
||||||
|
|
||||||
|
## 금지 사항
|
||||||
|
- DB 스키마 변경 시 반드시 마이그레이션 전략을 함께 고려한다.
|
||||||
|
- 비즈니스 로직을 DAO에 넣지 않는다.
|
||||||
43
agent-ops/rules/domain/flutter-app/rules.md
Normal file
43
agent-ops/rules/domain/flutter-app/rules.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
name: flutter-app
|
||||||
|
description: Flutter UI 레이어 - 앱 진입점, 화면 라우팅, 로그인, 상태관리 담당 (Core Domain)
|
||||||
|
type: core
|
||||||
|
---
|
||||||
|
|
||||||
|
# Flutter App
|
||||||
|
|
||||||
|
## 목적 / 책임
|
||||||
|
- Flutter 앱 진입점 및 위젯 트리 관리
|
||||||
|
- 화면 라우팅 (알림 탭 시 채널/DM 이동)
|
||||||
|
- 로그인 화면 및 인증 토큰 설정
|
||||||
|
- Firebase 초기화
|
||||||
|
|
||||||
|
## 포함 경로
|
||||||
|
- `lib/main.dart`
|
||||||
|
- `lib/` (서비스 파일 제외한 화면/위젯 코드)
|
||||||
|
|
||||||
|
## 제외 경로
|
||||||
|
- `lib/services/` (push-notification 도메인)
|
||||||
|
- `android/` (android-native 도메인)
|
||||||
|
|
||||||
|
## 주요 구성 요소
|
||||||
|
- `main.dart`: Firebase 초기화, PushNotificationService 세팅, 앱 위젯
|
||||||
|
- `MyApp`: MaterialApp 루트 위젯
|
||||||
|
- `MyHomePage`: 현재 기본 홈 화면 (TODO: 실제 라우터로 교체 필요)
|
||||||
|
|
||||||
|
## 유지할 패턴
|
||||||
|
- Firebase.initializeApp() 은 앱 시작 시 가장 먼저 호출
|
||||||
|
- PushNotificationService 초기화는 Firebase 초기화 후
|
||||||
|
|
||||||
|
## TODO (push-notification-todo.md 기준)
|
||||||
|
- **[TODO-2] 🔴**: 앱 라우터 및 화면 네비게이션 구현
|
||||||
|
- **[TODO-3] 🔴**: 로그인 화면 및 setAuthToken 연동
|
||||||
|
- **[TODO-4] 🟡**: 포그라운드 알림 UI 표시
|
||||||
|
|
||||||
|
## 다른 도메인과의 경계
|
||||||
|
- **push-notification**: 알림 탭 이벤트 수신은 push-notification, 이후 화면 이동은 flutter-app
|
||||||
|
- **android-native**: Flutter ↔ Android 통신은 EventChannel/MethodChannel 사용, 직접 수정 최소화
|
||||||
|
|
||||||
|
## 금지 사항
|
||||||
|
- Android 네이티브 파일을 이 도메인에서 직접 수정하지 않는다.
|
||||||
|
- 서비스 로직을 위젯 코드에 섞지 않는다.
|
||||||
51
agent-ops/rules/domain/push-notification/rules.md
Normal file
51
agent-ops/rules/domain/push-notification/rules.md
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
---
|
||||||
|
name: push-notification
|
||||||
|
description: FCM 기반 푸시 알림 수신, 표시, ACK 처리 담당 (Core Domain)
|
||||||
|
type: core
|
||||||
|
---
|
||||||
|
|
||||||
|
# Push Notification
|
||||||
|
|
||||||
|
## 목적 / 책임
|
||||||
|
- Firebase Cloud Messaging(FCM) 메시지 수신 및 처리
|
||||||
|
- 포그라운드/백그라운드/종료 상태별 알림 표시
|
||||||
|
- 알림 수신 ACK를 Mattermost 서버로 전달
|
||||||
|
- Flutter ↔ Android 간 이벤트 채널 통신
|
||||||
|
|
||||||
|
## 포함 경로
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/MattermostFirebaseMessagingService.kt`
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/helpers/CustomPushNotificationHelper.java`
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/helpers/NotificationHelper.kt`
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/NotificationReplyBroadcastReceiver.java`
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/NotificationDismissService.java`
|
||||||
|
- `android/app/src/main/java/com/tokilabs/mattermost/ReceiptDelivery.java`
|
||||||
|
- `lib/services/push_notification_service.dart`
|
||||||
|
- `lib/services/push_notification_background.dart`
|
||||||
|
|
||||||
|
## 제외 경로
|
||||||
|
- `lib/` 내 UI 화면 관련 코드 (flutter-app 도메인)
|
||||||
|
- `android/.../db/` (database 도메인)
|
||||||
|
- `android/.../helpers/Network.kt` (network 도메인)
|
||||||
|
|
||||||
|
## 주요 구성 요소
|
||||||
|
- `MattermostFirebaseMessagingService`: FCM 메시지 수신 진입점, 포그라운드/백그라운드 분기
|
||||||
|
- `CustomPushNotificationHelper`: 알림 UI 빌드, 아바타 이미지 처리
|
||||||
|
- `NotificationHelper`: 알림 ID, 그룹 요약 관리
|
||||||
|
- `ReceiptDelivery`: `/api/v4/notifications/ack` ACK 전송
|
||||||
|
- `PushNotificationService`: Flutter 싱글톤 서비스, EventChannel 수신
|
||||||
|
- `push_notification_background.dart`: 백그라운드 FCM 핸들러
|
||||||
|
|
||||||
|
## 유지할 패턴
|
||||||
|
- FCM 수신 → `MattermostFirebaseMessagingService` → `CustomPushNotificationHelper` 흐름
|
||||||
|
- EventChannel을 통한 Android → Flutter 이벤트 전달
|
||||||
|
- 알림 표시 전 반드시 ACK 전송 (`ReceiptDelivery`)
|
||||||
|
- Singleton 패턴: `PushNotificationService.instance`
|
||||||
|
|
||||||
|
## 다른 도메인과의 경계
|
||||||
|
- **database**: push-notification은 알림 데이터를 읽기만 하고, DB 스키마/쿼리 구현은 database 도메인
|
||||||
|
- **network**: HTTP 통신 구현은 network 도메인의 `Network.kt` 사용
|
||||||
|
- **flutter-app**: 알림 탭 시 화면 라우팅은 flutter-app 도메인
|
||||||
|
|
||||||
|
## 금지 사항
|
||||||
|
- DB 스키마 변경을 이 도메인에서 직접 하지 않는다.
|
||||||
|
- 알림 채널 설정은 `MainApplication.kt`(android-native 도메인)에서 한다.
|
||||||
23
agent-ops/rules/global.md
Normal file
23
agent-ops/rules/global.md
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
name: global
|
||||||
|
description: 전 프로젝트 공통 규칙
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Global Rules
|
||||||
|
|
||||||
|
## 응답 언어
|
||||||
|
- 한국어로 응답한다.
|
||||||
|
|
||||||
|
## 기존 구조 우선
|
||||||
|
- 새 파일 생성보다 기존 파일 수정을 우선한다.
|
||||||
|
- 기존 패턴과 컨벤션을 따른다.
|
||||||
|
|
||||||
|
## 코드 변경 원칙
|
||||||
|
- 코드 변경 전 관련 domain rule을 확인한다.
|
||||||
|
- 요청 범위를 넘는 변경을 금지한다.
|
||||||
|
- 불확실하면 단정하지 말고 후보로 제시한다.
|
||||||
|
|
||||||
|
## 작업 전 확인
|
||||||
|
- `agent-ops/skills/router.md`를 먼저 참조하여 필요한 skill 또는 domain rule만 로드한다.
|
||||||
|
- 관련 rule / skill만 최소로 로드한다.
|
||||||
30
agent-ops/skills/_templates/skill-template.md
Normal file
30
agent-ops/skills/_templates/skill-template.md
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
name: <skill-name>
|
||||||
|
description: <스킬 한 줄 설명>
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# <Skill Name>
|
||||||
|
|
||||||
|
## 목적
|
||||||
|
이 스킬이 해결하는 문제
|
||||||
|
|
||||||
|
## 언제 호출할지
|
||||||
|
- 이 스킬을 사용해야 하는 상황
|
||||||
|
|
||||||
|
## 입력
|
||||||
|
- 필요한 정보나 컨텍스트
|
||||||
|
|
||||||
|
## 먼저 확인할 것
|
||||||
|
- 실행 전 반드시 확인해야 할 사항
|
||||||
|
|
||||||
|
## 실행 절차
|
||||||
|
1. 첫 번째 단계
|
||||||
|
2. 두 번째 단계
|
||||||
|
3. ...
|
||||||
|
|
||||||
|
## 출력 형식
|
||||||
|
- 결과물의 형태 설명
|
||||||
|
|
||||||
|
## 금지 사항
|
||||||
|
- 이 스킬 실행 중 하면 안 되는 것
|
||||||
44
agent-ops/skills/code-change/SKILL.md
Normal file
44
agent-ops/skills/code-change/SKILL.md
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
---
|
||||||
|
name: code-change
|
||||||
|
description: 코드 변경 작업 전 domain rule 확인 및 변경 범위 검증
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Code Change
|
||||||
|
|
||||||
|
## 목적
|
||||||
|
코드 변경 시 도메인 규칙을 위반하지 않도록 변경 전 검증하고,
|
||||||
|
변경 범위를 요청 범위 내로 제한한다.
|
||||||
|
|
||||||
|
## 언제 호출할지
|
||||||
|
- 코드를 수정하기 전
|
||||||
|
- 새 파일을 생성하기 전
|
||||||
|
- 기존 패턴과 다른 방식으로 구현하려 할 때
|
||||||
|
|
||||||
|
## 입력
|
||||||
|
- 변경하려는 파일 경로
|
||||||
|
- 변경 내용 요약
|
||||||
|
|
||||||
|
## 먼저 확인할 것
|
||||||
|
1. `find-domain` 스킬로 도메인 판별
|
||||||
|
2. 해당 도메인의 `rules.md` 로드
|
||||||
|
3. `push-notification-todo.md` - 현재 TODO 상태 확인 (기존 TODO와 충돌 여부)
|
||||||
|
|
||||||
|
## 실행 절차
|
||||||
|
1. 도메인 판별 (find-domain 스킬 참조)
|
||||||
|
2. 도메인 rules.md의 **금지 사항** 확인
|
||||||
|
3. 도메인 rules.md의 **유지할 패턴** 확인
|
||||||
|
4. 변경이 요청 범위를 벗어나지 않는지 검토
|
||||||
|
5. 변경 후 영향받는 다른 도메인이 있으면 명시
|
||||||
|
6. 코드 변경 실행
|
||||||
|
|
||||||
|
## 출력 형식
|
||||||
|
- 변경 대상 파일
|
||||||
|
- 적용된 domain rule 요약
|
||||||
|
- 변경 내용
|
||||||
|
- 영향받는 도메인 (있는 경우)
|
||||||
|
|
||||||
|
## 금지 사항
|
||||||
|
- 요청 범위를 넘는 코드를 수정하지 않는다.
|
||||||
|
- domain rule의 금지 사항을 위반하지 않는다.
|
||||||
|
- TODO 항목을 무시하고 임의로 구현하지 않는다.
|
||||||
42
agent-ops/skills/find-domain/SKILL.md
Normal file
42
agent-ops/skills/find-domain/SKILL.md
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
---
|
||||||
|
name: find-domain
|
||||||
|
description: 변경 대상 파일/기능이 어느 도메인에 속하는지 판별
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Find Domain
|
||||||
|
|
||||||
|
## 목적
|
||||||
|
변경하려는 파일이나 기능이 어느 도메인에 속하는지 빠르게 판별하여
|
||||||
|
올바른 domain rule을 로드하도록 안내한다.
|
||||||
|
|
||||||
|
## 언제 호출할지
|
||||||
|
- 어떤 파일을 수정해야 할지는 알지만 도메인이 불명확할 때
|
||||||
|
- 여러 도메인에 걸쳐 있는 작업을 시작하기 전
|
||||||
|
|
||||||
|
## 입력
|
||||||
|
- 수정하려는 파일 경로 또는 기능 설명
|
||||||
|
|
||||||
|
## 먼저 확인할 것
|
||||||
|
- `agent-ops/skills/router.md`의 도메인 맵
|
||||||
|
|
||||||
|
## 실행 절차
|
||||||
|
1. 파일 경로를 `router.md` 도메인 맵과 대조한다.
|
||||||
|
2. 경로가 불명확하면 아래 기준으로 판별한다.
|
||||||
|
- `lib/services/` → push-notification
|
||||||
|
- `lib/` (그 외) → flutter-app
|
||||||
|
- `android/.../db/` → database
|
||||||
|
- `android/.../helpers/` → 파일별 담당 도메인 확인
|
||||||
|
- `android/...MainActivity`, `MainApplication`, `AppLifecycleTracker` → android-native
|
||||||
|
- `android/.../MattermostFirebaseMessagingService`, `ReceiptDelivery`, `NotificationReplyBroadcastReceiver` → push-notification
|
||||||
|
3. 해당 도메인의 `rules.md`를 로드한다.
|
||||||
|
4. 경계가 모호하면 후보 도메인을 제시하고 사용자에게 확인한다.
|
||||||
|
|
||||||
|
## 출력 형식
|
||||||
|
- 판별된 도메인 이름
|
||||||
|
- 로드할 rules.md 경로
|
||||||
|
- 경계가 모호한 경우 후보 목록
|
||||||
|
|
||||||
|
## 금지 사항
|
||||||
|
- 도메인을 단정 짓지 말고 모호하면 후보로 제시한다.
|
||||||
|
- 관련 없는 domain rule을 추가로 로드하지 않는다.
|
||||||
43
agent-ops/skills/router.md
Normal file
43
agent-ops/skills/router.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
name: router
|
||||||
|
description: 작업 유형별 필요한 skill / domain rule 라우팅
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Skill Router
|
||||||
|
|
||||||
|
작업 시작 전 이 파일을 먼저 읽고, 해당하는 skill 또는 domain rule만 로드한다.
|
||||||
|
|
||||||
|
## 라우팅 축
|
||||||
|
|
||||||
|
### 도메인 판별이 필요할 때
|
||||||
|
→ `agent-ops/skills/find-domain/SKILL.md`
|
||||||
|
|
||||||
|
### 코드 변경 작업
|
||||||
|
→ `agent-ops/skills/code-change/SKILL.md`
|
||||||
|
→ 변경 대상 도메인의 `agent-ops/rules/domain/<domain>/rules.md`
|
||||||
|
|
||||||
|
### 푸시 알림 관련 작업
|
||||||
|
→ `agent-ops/rules/domain/push-notification/rules.md`
|
||||||
|
|
||||||
|
### Flutter UI / 화면 / 라우팅 작업
|
||||||
|
→ `agent-ops/rules/domain/flutter-app/rules.md`
|
||||||
|
|
||||||
|
### Room DB / 데이터 저장 관련 작업
|
||||||
|
→ `agent-ops/rules/domain/database/rules.md`
|
||||||
|
|
||||||
|
### Android 진입점 / 채널 / 생명주기 작업
|
||||||
|
→ `agent-ops/rules/domain/android-native/rules.md`
|
||||||
|
|
||||||
|
### 전체 구조 파악 / 스캐폴드
|
||||||
|
→ `agent-ops/rules/global.md`
|
||||||
|
→ 도메인 전체 rules.md 순차 로드
|
||||||
|
|
||||||
|
## 도메인 맵
|
||||||
|
|
||||||
|
| 도메인 | 경로 | 유형 |
|
||||||
|
|--------|------|------|
|
||||||
|
| push-notification | `agent-ops/rules/domain/push-notification/rules.md` | Core |
|
||||||
|
| flutter-app | `agent-ops/rules/domain/flutter-app/rules.md` | Core |
|
||||||
|
| database | `agent-ops/rules/domain/database/rules.md` | Supporting |
|
||||||
|
| android-native | `agent-ops/rules/domain/android-native/rules.md` | Supporting |
|
||||||
432
push-notification-todo.md
Normal file
432
push-notification-todo.md
Normal file
|
|
@ -0,0 +1,432 @@
|
||||||
|
# Push Notification Migration TODO
|
||||||
|
|
||||||
|
## 배경
|
||||||
|
|
||||||
|
mattermost-mobile(React Native) 프로젝트의 안드로이드 푸시 알림 시스템을 이 Flutter 프로젝트(nomadcode)로 마이그레이션한 작업 기록 및 잔여 TODO.
|
||||||
|
|
||||||
|
**패키지명:** `com.tokilabs.mattermost` (google-services.json과 일치)
|
||||||
|
**원본 참조:** `/config/workspace/mattermost-mobile`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 전체 아키텍처 요약
|
||||||
|
|
||||||
|
```
|
||||||
|
FCM 서버
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
MattermostFirebaseMessagingService.kt ← 백그라운드/포그라운드 FCM 수신
|
||||||
|
│
|
||||||
|
├─ JWT 서명 검증 (CustomPushNotificationHelper.java)
|
||||||
|
├─ ReceiptDelivery.java → /api/v4/notifications/ack POST
|
||||||
|
├─ PushNotificationDataHelper.kt → Room DB에서 채널/포스트/유저 조회
|
||||||
|
├─ CustomPushNotificationHelper.java → 시스템 알림 표시
|
||||||
|
│
|
||||||
|
└─ EventChannel (com.tokilabs.mattermost/notifications)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
push_notification_service.dart ← Flutter에서 이벤트 수신
|
||||||
|
│
|
||||||
|
├─ onNotification stream → UI에서 포그라운드 알림 표시
|
||||||
|
└─ onNotificationOpened stream → 알림 탭 시 채널/스레드 이동
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
main.dart (onNavigateToChannel / onNavigateToThread 콜백)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 완료된 작업 (건드리지 말 것)
|
||||||
|
|
||||||
|
### Android 네이티브 레이어 ✅
|
||||||
|
| 파일 | 역할 |
|
||||||
|
|------|------|
|
||||||
|
| `MattermostFirebaseMessagingService.kt` | FCM 수신, JWT 검증, Flutter EventSink로 전달 |
|
||||||
|
| `helpers/CustomPushNotificationHelper.java` | 알림 채널 생성, 시스템 알림 빌드, 아바타 처리 |
|
||||||
|
| `helpers/NotificationHelper.kt` | 알림 ID 관리, 그룹 알림 요약 |
|
||||||
|
| `helpers/DatabaseHelper.kt` | GlobalDatabase + MattermostDatabase 접근 |
|
||||||
|
| `helpers/Network.kt` | OkHttp 기반 HTTP 클라이언트 (토큰 캐시 포함) |
|
||||||
|
| `helpers/db/GlobalDatabase.kt` | deviceToken, Servers 테이블 |
|
||||||
|
| `helpers/db/MattermostDatabase.kt` | Config, User, System 테이블 (서버별) |
|
||||||
|
| `helpers/db/GlobalEntities.kt` | GlobalEntity, ServerEntity, DAO 정의 |
|
||||||
|
| `helpers/db/ServerEntities.kt` | ConfigEntity, UserEntity, SystemEntity, DAO 정의 |
|
||||||
|
| `ReceiptDelivery.java` | `/api/v4/notifications/ack` ACK 전송 |
|
||||||
|
| `NotificationReplyBroadcastReceiver.java` | 알림에서 인라인 답장 처리 |
|
||||||
|
| `NotificationDismissService.java` | 알림 삭제 처리 |
|
||||||
|
| `AppLifecycleTracker.kt` | 앱 포그라운드/백그라운드 상태 추적 |
|
||||||
|
| `MainApplication.kt` | DB 초기화, 알림 채널 생성, 라이프사이클 추적 |
|
||||||
|
| `MainActivity.kt` | EventChannel + MethodChannel 등록 |
|
||||||
|
|
||||||
|
### Flutter 레이어 ✅
|
||||||
|
| 파일 | 역할 |
|
||||||
|
|------|------|
|
||||||
|
| `lib/services/push_notification_service.dart` | 싱글톤, 채널 수신, 스트림 노출, MethodChannel 호출 |
|
||||||
|
| `lib/services/push_notification_background.dart` | 백그라운드 FCM 핸들러 |
|
||||||
|
| `lib/main.dart` | Firebase 초기화, PushNotificationService 초기화, 네비게이션 콜백 등록 |
|
||||||
|
|
||||||
|
### 리소스 ✅
|
||||||
|
- `mipmap-*/ic_launcher.png` + `ic_launcher_round.png` + `ic_launcher_background.png` + `ic_launcher_foreground.png`
|
||||||
|
- `mipmap-anydpi-v26/ic_launcher.xml` + `ic_launcher_round.xml` (Adaptive Icon)
|
||||||
|
- `drawable-*/ic_notif_action_reply.png` (답장 버튼 아이콘)
|
||||||
|
- `mipmap-*/ic_notification.png` (알림 상태바 아이콘)
|
||||||
|
- `AndroidManifest.xml` — 권한, 서비스, 리시버 등록 완료
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 잔여 TODO
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [TODO-1] Android: PushNotificationDataHelper.kt — Room DAO 실제 쿼리 구현
|
||||||
|
|
||||||
|
**파일:** `android/app/src/main/java/com/tokilabs/mattermost/helpers/PushNotificationDataHelper.kt`
|
||||||
|
|
||||||
|
현재 다음 메서드들이 `Log.i(...) return null` stub 상태:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
private fun fetchTeamIfNeeded(db: MattermostDatabase, serverUrl: String, teamId: String): Bundle?
|
||||||
|
private fun fetchMyChannel(db: MattermostDatabase, channelId: String): Bundle?
|
||||||
|
private fun fetchPosts(db: MattermostDatabase, serverUrl: String, channelId: String, isCRTEnabled: Boolean, rootId: String?): Bundle?
|
||||||
|
private fun fetchThread(db: MattermostDatabase, serverUrl: String, rootId: String): Bundle?
|
||||||
|
private fun fetchNeededUsers(serverUrl: String, channelId: String): List<Bundle>?
|
||||||
|
private fun saveToDatabase(db: MattermostDatabase, data: Bundle, teamId: String?, channelId: String?, isCRTEnabled: Boolean)
|
||||||
|
```
|
||||||
|
|
||||||
|
**구현 방법:**
|
||||||
|
|
||||||
|
1. `ServerEntities.kt`에 아래 Entity + DAO 추가:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Channel 테이블
|
||||||
|
@Entity(tableName = "Channel")
|
||||||
|
data class ChannelEntity(
|
||||||
|
@PrimaryKey val id: String,
|
||||||
|
val display_name: String? = null,
|
||||||
|
val type: String? = null, // O=public, P=private, D=DM, G=Group
|
||||||
|
val team_id: String? = null,
|
||||||
|
val delete_at: Long = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface ChannelDao {
|
||||||
|
@Query("SELECT * FROM Channel WHERE id = :id LIMIT 1")
|
||||||
|
fun getChannel(id: String): ChannelEntity?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun upsert(entity: ChannelEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post 테이블
|
||||||
|
@Entity(tableName = "Post")
|
||||||
|
data class PostEntity(
|
||||||
|
@PrimaryKey val id: String,
|
||||||
|
val channel_id: String? = null,
|
||||||
|
val root_id: String? = null,
|
||||||
|
val user_id: String? = null,
|
||||||
|
val message: String? = null,
|
||||||
|
val create_at: Long = 0,
|
||||||
|
val delete_at: Long = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface PostDao {
|
||||||
|
@Query("SELECT * FROM Post WHERE channel_id = :channelId ORDER BY create_at DESC LIMIT 20")
|
||||||
|
fun getPostsForChannel(channelId: String): List<PostEntity>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Post WHERE root_id = :rootId ORDER BY create_at ASC")
|
||||||
|
fun getThreadPosts(rootId: String): List<PostEntity>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun upsert(entity: PostEntity)
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun upsertAll(entities: List<PostEntity>)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Team 테이블
|
||||||
|
@Entity(tableName = "Team")
|
||||||
|
data class TeamEntity(
|
||||||
|
@PrimaryKey val id: String,
|
||||||
|
val display_name: String? = null,
|
||||||
|
val name: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface TeamDao {
|
||||||
|
@Query("SELECT * FROM Team WHERE id = :id LIMIT 1")
|
||||||
|
fun getTeam(id: String): TeamEntity?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun upsert(entity: TeamEntity)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. `MattermostDatabase.kt`에 새 DAO 등록:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Database(
|
||||||
|
entities = [ConfigEntity::class, UserEntity::class, SystemEntity::class,
|
||||||
|
ChannelEntity::class, PostEntity::class, TeamEntity::class],
|
||||||
|
version = 2, // 버전 올리기
|
||||||
|
exportSchema = false
|
||||||
|
)
|
||||||
|
abstract class MattermostDatabase : RoomDatabase() {
|
||||||
|
abstract fun configDao(): ConfigDao
|
||||||
|
abstract fun userDao(): UserDao
|
||||||
|
abstract fun systemDao(): SystemDao
|
||||||
|
abstract fun channelDao(): ChannelDao
|
||||||
|
abstract fun postDao(): PostDao
|
||||||
|
abstract fun teamDao(): TeamDao
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. `PushNotificationDataHelper.kt` stub 메서드 구현:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
private fun fetchMyChannel(db: MattermostDatabase, channelId: String): Bundle? {
|
||||||
|
val channel = db.channelDao().getChannel(channelId) ?: return null
|
||||||
|
return Bundle().apply {
|
||||||
|
putString("id", channel.id)
|
||||||
|
putString("display_name", channel.display_name)
|
||||||
|
putString("type", channel.type)
|
||||||
|
putString("team_id", channel.team_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchPosts(db: MattermostDatabase, serverUrl: String, channelId: String,
|
||||||
|
isCRTEnabled: Boolean, rootId: String?): Bundle? {
|
||||||
|
val posts = if (isCRTEnabled && rootId != null)
|
||||||
|
db.postDao().getThreadPosts(rootId)
|
||||||
|
else
|
||||||
|
db.postDao().getPostsForChannel(channelId)
|
||||||
|
if (posts.isEmpty()) return null
|
||||||
|
return Bundle().apply {
|
||||||
|
posts.forEachIndexed { i, post ->
|
||||||
|
putBundle("post_$i", Bundle().apply {
|
||||||
|
putString("id", post.id)
|
||||||
|
putString("message", post.message)
|
||||||
|
putString("user_id", post.user_id)
|
||||||
|
putLong("create_at", post.create_at)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchTeamIfNeeded(db: MattermostDatabase, serverUrl: String, teamId: String): Bundle? {
|
||||||
|
val team = db.teamDao().getTeam(teamId) ?: return null
|
||||||
|
return Bundle().apply {
|
||||||
|
putString("id", team.id)
|
||||||
|
putString("display_name", team.display_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchThread(db: MattermostDatabase, serverUrl: String, rootId: String): Bundle? {
|
||||||
|
val posts = db.postDao().getThreadPosts(rootId)
|
||||||
|
if (posts.isEmpty()) return null
|
||||||
|
return Bundle().apply { putInt("reply_count", posts.size) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchNeededUsers(serverUrl: String, channelId: String): List<Bundle>? {
|
||||||
|
// Note: 현재 DB에 channel_members 테이블 없음 → 추후 구현
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveToDatabase(db: MattermostDatabase, data: Bundle, teamId: String?,
|
||||||
|
channelId: String?, isCRTEnabled: Boolean) {
|
||||||
|
// Flutter 부팅 전 수신된 알림 데이터 저장
|
||||||
|
// System 테이블에 "pendingNotification_{channelId}" 키로 JSON 저장 권장
|
||||||
|
channelId ?: return
|
||||||
|
val json = org.json.JSONObject().apply {
|
||||||
|
data.keySet()?.forEach { key -> put(key, data.getString(key)) }
|
||||||
|
}
|
||||||
|
db.systemDao().upsert(SystemEntity("pendingNotification_$channelId", json.toString()))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**주의:** `db.close()` 호출이 `PushNotificationDataHelper.kt` 99번째 줄 finally 블록에 있음.
|
||||||
|
Room은 싱글톤이므로 `close()` 호출 시 이후 쿼리가 실패함. **해당 줄 제거 필요.**
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// 제거 대상 (PushNotificationDataHelper.kt:99)
|
||||||
|
db?.close() // ← 삭제
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [TODO-2] Flutter: 앱 라우터 및 화면 구현
|
||||||
|
|
||||||
|
**파일:** `lib/main.dart`
|
||||||
|
|
||||||
|
현재 상태:
|
||||||
|
```dart
|
||||||
|
pushService.onNavigateToChannel = (serverUrl, channelId) {
|
||||||
|
// TODO: 채널 화면으로 이동
|
||||||
|
print('[Nav] Navigate to channel: $channelId on $serverUrl');
|
||||||
|
};
|
||||||
|
pushService.onNavigateToThread = (serverUrl, rootId) {
|
||||||
|
// TODO: 스레드 화면으로 이동
|
||||||
|
print('[Nav] Navigate to thread: $rootId on $serverUrl');
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**구현 방향:**
|
||||||
|
|
||||||
|
1. go_router 또는 Navigator 2.0 기반 라우터 설정
|
||||||
|
2. 최소 필요 화면:
|
||||||
|
- `LoginScreen` — 서버 URL 입력 + 로그인 → `pushService.setAuthToken()` 호출
|
||||||
|
- `ChannelScreen` — 채널 메시지 목록
|
||||||
|
- `ThreadScreen` — 스레드 메시지 목록
|
||||||
|
|
||||||
|
3. `main.dart` 콜백 연결 예시:
|
||||||
|
```dart
|
||||||
|
pushService.onNavigateToChannel = (serverUrl, channelId) {
|
||||||
|
_navigatorKey.currentState?.pushNamed(
|
||||||
|
'/channel',
|
||||||
|
arguments: {'serverUrl': serverUrl, 'channelId': channelId},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
pushService.onNavigateToThread = (serverUrl, rootId) {
|
||||||
|
_navigatorKey.currentState?.pushNamed(
|
||||||
|
'/thread',
|
||||||
|
arguments: {'serverUrl': serverUrl, 'rootId': rootId},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [TODO-3] Flutter: 로그인 화면 및 setAuthToken 연결
|
||||||
|
|
||||||
|
알림 답장(NotificationReplyBroadcastReceiver)과 ACK(ReceiptDelivery)가 서버 인증 토큰을 필요로 함.
|
||||||
|
Flutter 로그인 성공 시 반드시 아래 호출 필요:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 로그인 성공 후
|
||||||
|
await PushNotificationService().setAuthToken(serverUrl, bearerToken);
|
||||||
|
|
||||||
|
// 로그아웃 시
|
||||||
|
await PushNotificationService().clearAuthToken(serverUrl);
|
||||||
|
```
|
||||||
|
|
||||||
|
이 호출이 없으면 인라인 답장 API와 ACK API 요청이 401로 실패함.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [TODO-4] Flutter: 포그라운드 알림 UI
|
||||||
|
|
||||||
|
앱이 포그라운드 상태일 때 FCM 메시지는 시스템 알림 대신 인앱 UI로 표시해야 함.
|
||||||
|
`push_notification_service.dart`의 `onNotification` 스트림을 구독해서 구현:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 예시: 메인 화면 또는 글로벌 오버레이에서
|
||||||
|
PushNotificationService().onNotification.listen((data) {
|
||||||
|
final type = data['type'];
|
||||||
|
if (type == 'message') {
|
||||||
|
// 상단 배너 또는 SnackBar 표시
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(data['message'] ?? '새 메시지')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [TODO-5] Flutter: firebase_options.dart 생성 (선택 사항)
|
||||||
|
|
||||||
|
현재 `main.dart`에서 `Firebase.initializeApp()`을 options 없이 호출 중 (google-services.json 사용).
|
||||||
|
FlutterFire CLI를 사용하면 더 명시적인 초기화 가능:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 프로젝트 루트에서
|
||||||
|
dart pub global activate flutterfire_cli
|
||||||
|
flutterfire configure --project=<firebase-project-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
실행 후 생성된 `lib/firebase_options.dart`를 사용:
|
||||||
|
```dart
|
||||||
|
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||||
|
```
|
||||||
|
|
||||||
|
현재도 동작에는 문제 없음. 멀티플랫폼(iOS 추가 시) 필수.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Flutter에서 사용 가능한 인터페이스 (push_notification_service.dart)
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final service = PushNotificationService();
|
||||||
|
|
||||||
|
// 초기화 (main에서 1회 호출)
|
||||||
|
service.init();
|
||||||
|
|
||||||
|
// 인증 토큰 관리
|
||||||
|
await service.setAuthToken(serverUrl, token, identifier: serverId);
|
||||||
|
await service.clearAuthToken(serverUrl);
|
||||||
|
|
||||||
|
// FCM 디바이스 토큰 조회
|
||||||
|
final token = await service.getDeviceToken();
|
||||||
|
|
||||||
|
// 포그라운드 알림 수신 스트림
|
||||||
|
service.onNotification.listen((Map<String, dynamic> data) { ... });
|
||||||
|
|
||||||
|
// 알림 탭 이벤트 스트림
|
||||||
|
service.onNotificationOpened.listen((NotificationOpenedEvent event) {
|
||||||
|
// event.serverUrl, event.channelId, event.rootId, event.isCRTEnabled
|
||||||
|
});
|
||||||
|
|
||||||
|
// 알림 탭 시 네비게이션 콜백 (main.dart builder에서 등록)
|
||||||
|
service.onNavigateToChannel = (serverUrl, channelId) { ... };
|
||||||
|
service.onNavigateToThread = (serverUrl, rootId) { ... };
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MethodChannel 등록 목록 (MainActivity.kt)
|
||||||
|
|
||||||
|
| 메서드명 | 인자 | 설명 |
|
||||||
|
|---------|------|------|
|
||||||
|
| `saveDeviceToken` | `token: String` | FCM 토큰 → Room GlobalDB 저장 |
|
||||||
|
| `getDeviceToken` | 없음 | Room GlobalDB에서 토큰 반환 |
|
||||||
|
| `setAuthToken` | `serverUrl`, `token`, `identifier?` | Network 캐시 + Room Servers 저장 |
|
||||||
|
| `clearAuthToken` | `serverUrl` | Network 캐시에서 토큰 제거 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## EventChannel 이벤트 타입 (com.tokilabs.mattermost/notifications)
|
||||||
|
|
||||||
|
네이티브에서 Flutter로 전달되는 Map의 `type` 필드:
|
||||||
|
|
||||||
|
| type | 설명 | 주요 필드 |
|
||||||
|
|------|------|---------|
|
||||||
|
| `message` | 새 메시지 알림 | `server_url`, `channel_id`, `post_id`, `root_id`, `message`, `sender_name`, `is_crt_enabled` |
|
||||||
|
| `clear` | 채널 읽음 처리 (알림 뱃지 초기화) | `server_url`, `channel_id` |
|
||||||
|
| `session` | 세션 만료 | `server_url` |
|
||||||
|
| `token_refresh` | FCM 토큰 갱신 | `token` |
|
||||||
|
| `opened` | 알림 탭으로 앱 진입 | `server_url`, `channel_id`, `root_id`, `is_crt_enabled`, `userInteraction: true` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 우선순위 정리
|
||||||
|
|
||||||
|
| 우선순위 | 항목 | 영향 |
|
||||||
|
|---------|------|------|
|
||||||
|
| 🔴 필수 | TODO-2: 라우터/화면 | 알림 탭 후 이동 불가 |
|
||||||
|
| 🔴 필수 | TODO-3: setAuthToken 연결 | 인라인 답장/ACK 인증 실패 |
|
||||||
|
| 🟡 권장 | TODO-1: Room DAO 구현 | 알림 표시는 되나 채널명/메시지 미리보기 없음 |
|
||||||
|
| 🟡 권장 | TODO-1의 db.close() 제거 | 간헐적 DB 접근 오류 |
|
||||||
|
| 🟢 선택 | TODO-4: 포그라운드 알림 UI | 포그라운드에서 알림 표시 없음 |
|
||||||
|
| 🟢 선택 | TODO-5: firebase_options.dart | iOS 추가 시 필요 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 참고: 원본 파일 위치
|
||||||
|
|
||||||
|
| 역할 | 원본 (mattermost-mobile) |
|
||||||
|
|------|--------------------------|
|
||||||
|
| FCM 서비스 | `android/app/src/main/java/com/tokilabs/mattermost/CustomPushNotification.kt` |
|
||||||
|
| 알림 헬퍼 | `android/app/src/main/java/com/mattermost/helpers/CustomPushNotificationHelper.java` |
|
||||||
|
| 데이터 헬퍼 | `android/app/src/main/java/com/mattermost/helpers/PushNotificationDataHelper.kt` |
|
||||||
|
| ACK 전송 | `android/app/src/main/java/com/tokilabs/mattermost/ReceiptDelivery.java` |
|
||||||
|
| Flutter 서비스 | `app/utils/push_notifications.ts` |
|
||||||
Loading…
Reference in a new issue