docs: 문서 간소화 (407줄 → 111줄)
This commit is contained in:
parent
ff15e1d18f
commit
51d215f16b
@ -1,335 +1,64 @@
|
||||
# Gmail Passport Calendar Scope 재연동 가이드
|
||||
# Gmail Calendar Scope 및 Timezone 트러블슈팅
|
||||
|
||||
**작성일**: 2025-11-14
|
||||
**작성일**: 2025-11-14 ~ 2025-11-16
|
||||
**작성자**: Claude
|
||||
**목적**: skill-calendar를 위한 Google Calendar API scope 추가
|
||||
|
||||
---
|
||||
|
||||
## 1. 현재 상황
|
||||
## 1. Calendar Scope 추가 (2025-11-14)
|
||||
|
||||
### 문제
|
||||
- skill-calendar가 gmail_token 조회는 성공
|
||||
- Google Calendar API 호출 시 `invalid_grant` 에러 발생
|
||||
- 원인: 현재 Gmail Passport scope에 Calendar API 권한 없음
|
||||
- skill-calendar가 Google Calendar API 호출 시 `invalid_grant` 에러
|
||||
- 원인: Gmail Passport scope에 Calendar API 권한 없음
|
||||
|
||||
### 필요한 Scope
|
||||
### 해결
|
||||
**auth-server scope 수정** (`app/providers/gmail_passport.py`):
|
||||
```python
|
||||
# 기존 (auth-server/app/providers/gmail_passport.py)
|
||||
GMAIL_API_SCOPES = [
|
||||
"https://www.googleapis.com/auth/gmail.send",
|
||||
"https://www.googleapis.com/auth/gmail.readonly",
|
||||
"https://www.googleapis.com/auth/gmail.modify",
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
"https://www.googleapis.com/auth/userinfo.profile"
|
||||
]
|
||||
|
||||
# 추가 필요
|
||||
+ "https://www.googleapis.com/auth/calendar",
|
||||
+ "https://www.googleapis.com/auth/calendar.events"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 재연동 방법
|
||||
|
||||
### 방법 1: auth-server scope 수정 후 재인증 (권장)
|
||||
|
||||
#### Step 1: auth-server scope 수정
|
||||
```bash
|
||||
# 51123 서버 접속 (auth-server는 51123에서 실행)
|
||||
ssh admin@192.168.219.45
|
||||
|
||||
# auth-server 디렉토리로 이동
|
||||
cd /home/admin/auth-server
|
||||
|
||||
# gmail_passport.py 수정
|
||||
vi app/providers/gmail_passport.py
|
||||
```
|
||||
|
||||
**수정 내용**:
|
||||
```python
|
||||
# Line 찾아서 수정 (대략 30-40줄)
|
||||
GMAIL_API_SCOPES = [
|
||||
"https://www.googleapis.com/auth/gmail.send",
|
||||
"https://www.googleapis.com/auth/gmail.readonly",
|
||||
"https://www.googleapis.com/auth/gmail.modify",
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
"https://www.googleapis.com/auth/calendar", # 추가
|
||||
"https://www.googleapis.com/auth/calendar.events" # 추가
|
||||
"https://www.googleapis.com/auth/calendar", # 추가
|
||||
"https://www.googleapis.com/auth/calendar.events" # 추가
|
||||
]
|
||||
```
|
||||
|
||||
#### Step 2: auth-server 재배포
|
||||
**재배포 및 재인증**:
|
||||
```bash
|
||||
# Git 커밋
|
||||
cd /home/admin/auth-server
|
||||
git add app/providers/gmail_passport.py
|
||||
git commit -m "feat: Gmail Passport에 Calendar scope 추가"
|
||||
git commit -m "feat: Calendar scope 추가"
|
||||
git push origin main
|
||||
|
||||
# Gitea Actions가 자동 배포 (30초 소요)
|
||||
# 또는 수동 배포:
|
||||
docker compose restart
|
||||
```
|
||||
|
||||
#### Step 3: 사용자별 재인증
|
||||
|
||||
**웹 브라우저에서**:
|
||||
1. https://ro-being.com 로그인 (김종태 계정)
|
||||
2. 설정 > 아이템 > Gmail
|
||||
3. "재인증" 버튼 클릭
|
||||
4. Google 권한 동의 화면에서 Calendar 권한 확인
|
||||
5. "허용" 클릭
|
||||
6. 자동으로 인벤토리 페이지로 리다이렉트
|
||||
7. gmail_token 테이블에 새로운 scope 저장됨
|
||||
|
||||
**확인**:
|
||||
```bash
|
||||
# 51124 서버에서
|
||||
cd /home/admin/ivada_project/skill-calendar
|
||||
python3 tests/test_google_calendar_integration.py
|
||||
```
|
||||
|
||||
### 방법 2: DB 직접 수정 (비권장, 임시)
|
||||
|
||||
#### 주의
|
||||
- OAuth 토큰은 발급 시 scope가 고정됨
|
||||
- DB만 수정해도 Google API는 scope 부족으로 거부
|
||||
- 반드시 재인증 필요
|
||||
|
||||
#### 확인용 쿼리
|
||||
```sql
|
||||
-- 현재 scope 확인
|
||||
SELECT user_id, scopes FROM gmail_token
|
||||
WHERE user_id = '53529291-5050-4daa-89fb-008b546feb63';
|
||||
|
||||
-- scope 추가 (효과 없음, 재인증 필요)
|
||||
UPDATE gmail_token
|
||||
SET scopes = scopes || '["https://www.googleapis.com/auth/calendar"]'::jsonb
|
||||
WHERE user_id = '53529291-5050-4daa-89fb-008b546feb63';
|
||||
```
|
||||
웹에서 Gmail 재인증 → Google 권한 동의 → Calendar scope 확인 → 허용
|
||||
|
||||
---
|
||||
|
||||
## 3. Google Cloud Console 설정 확인
|
||||
## 2. Calendar CRUD Timezone 이슈 (2025-11-16)
|
||||
|
||||
### OAuth 동의 화면 - Calendar Scope 추가 (중요!)
|
||||
### 문제
|
||||
- **CREATE**: 성공
|
||||
- **READ**: 방금 등록한 일정 조회 실패
|
||||
- **DELETE**: 성공하지만 잘못된 날짜 표시
|
||||
|
||||
**현재 상태**: Gmail scope만 있음 (gmail.modify, userinfo.email 등)
|
||||
|
||||
**추가 절차**:
|
||||
1. https://console.cloud.google.com/apis/credentials/consent
|
||||
2. 화면 하단 "범위 추가 또는 삭제" 버튼 클릭
|
||||
3. 필터에서 "calendar" 검색
|
||||
4. 다음 2개 체크:
|
||||
- ✅ `.../auth/calendar` - 캘린더 보기, 수정, 공유, 영구 삭제
|
||||
- ✅ `.../auth/calendar.events` - 캘린더 일정 보기 및 수정
|
||||
5. "업데이트" 버튼 클릭
|
||||
6. "저장 후 계속" 클릭
|
||||
7. 완료 확인: "민감한 범위" 또는 "제한된 범위"에 Calendar 표시됨
|
||||
|
||||
### API 활성화
|
||||
1. "API 및 서비스" > "라이브러리"
|
||||
2. "Google Calendar API" 검색
|
||||
3. "사용 설정" 클릭 (이미 활성화되어 있을 수 있음)
|
||||
|
||||
---
|
||||
|
||||
## 4. 테스트 시나리오
|
||||
|
||||
### E2E 테스트
|
||||
```
|
||||
1. auth-server scope 수정 완료
|
||||
2. 김종태 계정 Gmail 재인증
|
||||
3. ro-being.com 로그인
|
||||
4. 로빙에게 메시지: "11월 24일 검진 07:40~12시 인천 연수구 갯벌로156"
|
||||
5. 로빙 응답: "일정을 구글 캘린더에 등록해드릴까요?"
|
||||
6. 사용자 응답: "그래"
|
||||
7. 로빙 응답: "✅ 구글 캘린더에 일정을 등록했습니다!"
|
||||
8. Google Calendar 웹에서 확인: 2025-11-24 07:40~12:00 일정 존재
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 트러블슈팅
|
||||
|
||||
### invalid_grant 계속 발생
|
||||
- Gmail Passport 재인증 확인
|
||||
- gmail_token.scopes 필드에 calendar 포함 확인
|
||||
- access_token 갱신 시도 (refresh_token 사용)
|
||||
|
||||
### Calendar API 403 Forbidden
|
||||
- Google Cloud Console에서 Calendar API 활성화 확인
|
||||
- OAuth 동의 화면에 Calendar scope 추가 확인
|
||||
|
||||
### 일정 등록 성공했지만 Calendar에 없음
|
||||
- 다른 Google 계정으로 로그인했을 가능성
|
||||
- primary calendar 대신 다른 캘린더 ID 사용 여부 확인
|
||||
|
||||
---
|
||||
|
||||
## 6. 참고 문서
|
||||
|
||||
- DOCS/troubleshooting/250820_happybell80_Gmail패스포트시스템완성.md
|
||||
- DOCS/plans/251114_skill_calendar_multiplatform_integration.md
|
||||
- Google Calendar API: https://developers.google.com/calendar/api/guides/overview
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 7. 테스트 결과 (2025-11-14)
|
||||
|
||||
### Calendar Scope 추가 성공 ✅
|
||||
- auth-server에 calendar scope 추가 완료
|
||||
- 김종태 계정 Gmail 재인증 완료
|
||||
- Google Cloud Console Calendar API 활성화 완료
|
||||
- skill-calendar → Google Calendar API 이벤트 생성 성공
|
||||
|
||||
### Calendar 통합 테스트 결과 ⚠️
|
||||
|
||||
#### 성공 케이스 (4/10)
|
||||
- ✅ "11월 30일 오후 2시 서울역에서 클라이언트 **미팅**" → calendar_event 감지됨
|
||||
- ✅ "11월 18일 오전 11시 사무실에서 **회의**" → calendar_event 감지됨
|
||||
- ✅ skill-calendar API 직접 호출 시 정상 작동
|
||||
- ✅ Google Calendar에 실제 이벤트 생성 확인
|
||||
|
||||
#### 실패 케이스 (6/10)
|
||||
- ❌ "11월 20일 오후 3시에 팀 미팅" → intent 감지 실패 (LLM으로 우회)
|
||||
- ❌ "11월 23일 오후 3시 **카페**" → intent 감지 실패 ("카페"는 패턴에 없음)
|
||||
- ❌ "11월 18일 14시 개발팀 **스프린트**" → intent 감지 실패 ("스프린트"는 패턴에 없음)
|
||||
- ❌ 승인 응답 ("네", "좋아", "ㅇㅇ") → approval로 변환 안됨 (컨텍스트 비어있음)
|
||||
|
||||
### 근본 원인 분석
|
||||
|
||||
#### 1. Intent 패턴 부족
|
||||
### 원인
|
||||
naive datetime (timezone 없음) 사용:
|
||||
```python
|
||||
# decision_engine.py의 calendar_event 패턴
|
||||
IntentType.CALENDAR_EVENT: [
|
||||
r"\d+월\s*\d+일.*(검진|미팅|회의|회식|약속|일정)", # 현재
|
||||
# 부족: 카페, 스프린트, 약속, 미팅, 회의, 점심, 저녁, 피팅 등
|
||||
]
|
||||
```
|
||||
|
||||
**문제**: 일정 키워드가 제한적이라 다양한 표현 감지 못함
|
||||
**해결**: 패턴 확장 + LLM fallback 활용
|
||||
|
||||
#### 2. Recent Conversations 로딩 실패
|
||||
```
|
||||
[DecisionEngine] 의도 분석 시작: '네'
|
||||
Loaded 0 recent conversations for user ...
|
||||
[Approval] 직전 intent 없음 → UNKNOWN
|
||||
```
|
||||
|
||||
**문제**:
|
||||
- PostgreSQL `conversation_log_pk` 중복 키 에러 발생 중
|
||||
- 따라서 conversation_log에 저장이 안 되고, 조회도 빈 배열
|
||||
- approval 로직은 recent_conversations에 의존하므로 작동 불가
|
||||
|
||||
**근본 원인**:
|
||||
- `conversation_log` 테이블의 `id` 시퀀스가 out-of-sync
|
||||
- 이미 존재하는 id (1149, 1151, 1153 등)를 insert하려고 시도
|
||||
|
||||
**해결 방안**:
|
||||
```sql
|
||||
-- 51123 서버에서
|
||||
SELECT MAX(id) FROM conversation_log; -- 현재 최대값 확인
|
||||
SELECT nextval('conversation_log_id_seq'); -- 시퀀스 현재값 확인
|
||||
-- 시퀀스를 MAX(id) + 1로 재설정
|
||||
SELECT setval('conversation_log_id_seq', (SELECT MAX(id) FROM conversation_log) + 1);
|
||||
```
|
||||
|
||||
### 개선 사항
|
||||
|
||||
#### 우선순위 1: DB 시퀀스 수정 (필수)
|
||||
- 없이는 approval 로직 완전 불능
|
||||
- PostgreSQL 시퀀스 재정렬 필요
|
||||
|
||||
#### 우선순위 2: Intent 패턴 확장
|
||||
```python
|
||||
IntentType.CALENDAR_EVENT: [
|
||||
# 기존
|
||||
r"\d+월\s*\d+일.*(검진|미팅|회의|회식|약속|일정)",
|
||||
# 추가
|
||||
r"\d+월\s*\d+일.*\d{1,2}시", # 시간만 있으면 일정으로 간주
|
||||
r"(내일|모레|다음주).*(시|점심|저녁|미팅|회의|약속)",
|
||||
r"\d+월\s*\d+일", # 날짜만 있어도 일정 가능성 고려
|
||||
]
|
||||
```
|
||||
|
||||
#### 우선순위 3: LLM Fallback 강화
|
||||
- calendar_confirm이 이미 LLM 기반으로 파싱 수행
|
||||
- Intent 감지 실패 시 LLM에게 "일정인가?" 질문 후 calendar_confirm 호출
|
||||
|
||||
---
|
||||
|
||||
**작업 완료**: Gmail Calendar 재인증 성공, skill-calendar API 작동 확인
|
||||
**다음 단계**:
|
||||
1. PostgreSQL `conversation_log` 시퀀스 수정 (필수)
|
||||
2. calendar_event 패턴 확장 (선택)
|
||||
3. 전체 E2E 재테스트
|
||||
|
||||
---
|
||||
|
||||
## 8. 추가 관찰 (2025-11-14 19:00)
|
||||
|
||||
### 신규 개선 사항
|
||||
- `conversation_log` 조회 시 intent 값 포함 → approval 로직이 다시 작동하여 `calendar_approval → create_event` 흐름이 복구됨
|
||||
- `calendar_handler`가 `calendar_confirm` 대화 기록을 직접 찾고, Markdown 필드가 없어도 날짜/시간/장소를 추출하도록 보완함
|
||||
- Slack/Frontend에서 들어온 승인 메시지(네/좋아/ㅇㅋ 등)는 즉시 캘린더 이벤트 생성으로 이어지고 Google Calendar에도 등록 확인
|
||||
|
||||
### 남은 문제
|
||||
- 승인 직후에도 LLM fallback이 한 번 더 호출되어 “응답을 생성할 수 없습니다.” 메시지가 덧붙음 → approval 분기에서 LLM 재호출을 막아야 함
|
||||
- 동일한 일정 문구를 반복 입력하면 매번 새로운 이벤트가 생성됨 → 일정 중복 체크 로직 필요
|
||||
- 문서화 미완료: 위 보완 사항을 정리했으나, Slack/Frontend 사용자 가이드는 아직 업데이트되지 않음 (향후 DOCS/guide 추가 필요)
|
||||
|
||||
### 후속 TODO
|
||||
1. approval 완료 후에는 LLM을 재호출하지 않고 "등록 완료" 응답을 직접 반환
|
||||
2. 일정 중복 감지(최근 N분 내 같은 날짜/시간이면 안내만) 기능 추가
|
||||
3. 사용자용 가이드 문서(DOCS/guide)에 승인 흐름 및 실패 메시지 내용을 반영
|
||||
|
||||
---
|
||||
|
||||
## 9. Calendar CRUD Timezone 이슈 해결 (2025-11-16)
|
||||
|
||||
### 문제 상황
|
||||
- **CREATE**: 일정 등록 성공 (event_id 반환, Google Calendar 확인됨)
|
||||
- **READ**: 방금 등록한 일정 조회 실패 ("11월 17일에 등록된 일정이 없습니다")
|
||||
- **DELETE**: 삭제는 성공하지만 잘못된 날짜 표시 ("2025-11-18 00:00" 대신 "2025-11-17 15:00"이어야 함)
|
||||
|
||||
### 근본 원인
|
||||
```python
|
||||
# 문제: naive datetime (timezone 없음) 사용
|
||||
# 문제
|
||||
start_iso = f"{date}T{start_hour}:{start_min}:00" # "2025-11-17T15:00:00"
|
||||
end_iso = f"{date}T{end_hour}:{end_min}:00" # "2025-11-17T16:00:00"
|
||||
|
||||
# Google Calendar API는 timezone 없는 datetime을 어떻게 해석할지 불확실
|
||||
# skill-calendar 서비스가 Asia/Seoul로 해석하지만,
|
||||
# rb8001에서 보내는 query datetime은 naive여서 매칭 실패
|
||||
# Google Calendar API는 timezone 없으면 해석 불확실
|
||||
# rb8001 → skill-calendar 전송 시 timezone 불일치
|
||||
```
|
||||
|
||||
### 증상
|
||||
1. **CREATE API 호출**:
|
||||
- 전송: `{"start": "2025-11-17T15:00:00", "end": "2025-11-17T16:00:00"}`
|
||||
- Google Calendar: Asia/Seoul timezone으로 저장됨
|
||||
### 해결
|
||||
**rb8001/app/router/calendar_handler.py 수정**:
|
||||
|
||||
2. **READ API 호출**:
|
||||
- 전송: `{"start_date": "2025-11-17T00:00:00", "end_date": "2025-11-17T23:59:59"}`
|
||||
- skill-calendar: 이 datetime을 어떤 timezone으로 해석?
|
||||
- 결과: 조회 실패 (timezone 불일치)
|
||||
|
||||
### 해결 방법
|
||||
|
||||
#### 수정 파일: `rb8001/app/router/calendar_handler.py`
|
||||
|
||||
**1. parse_time_range() 함수 (Line 397-432)**
|
||||
**parse_time_range()** (Line 397):
|
||||
```python
|
||||
# Before
|
||||
start_iso = f"{date}T{start_hour.zfill(2)}:{start_min}:00"
|
||||
end_iso = f"{date}T{end_hour.zfill(2)}:{end_min}:00"
|
||||
|
||||
# After
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
@ -340,67 +69,42 @@ start_dt = datetime(year, month, day, int(start_hour), int(start_min), 0, tzinfo
|
||||
end_dt = datetime(year, month, day, int(end_hour), int(end_min), 0, tzinfo=tz)
|
||||
|
||||
return start_dt.isoformat(), end_dt.isoformat()
|
||||
# Result: "2025-11-17T15:00:00+09:00", "2025-11-17T16:00:00+09:00"
|
||||
# "2025-11-17T15:00:00+09:00"
|
||||
```
|
||||
|
||||
**2. handle_calendar_query() 함수 (Line 228-312)**
|
||||
**handle_calendar_query()** (Line 228):
|
||||
```python
|
||||
# Before
|
||||
today = datetime.now()
|
||||
target_date = datetime(year, month, day)
|
||||
|
||||
# After
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
today = datetime.now(ZoneInfo("Asia/Seoul"))
|
||||
target_date = datetime(year, month, day, tzinfo=ZoneInfo("Asia/Seoul"))
|
||||
|
||||
start_time = target_date.replace(hour=0, minute=0, second=0)
|
||||
end_time = target_date.replace(hour=23, minute=59, second=59)
|
||||
# Result: start_time.isoformat() = "2025-11-17T00:00:00+09:00"
|
||||
```
|
||||
|
||||
### 테스트 결과
|
||||
**Before (67%)**:
|
||||
- ✅ CREATE
|
||||
- ❌ READ (조회 실패)
|
||||
- ✅ DELETE
|
||||
|
||||
**CRUD 테스트 (test_calendar_crud_proper.py)**:
|
||||
```bash
|
||||
docker exec rb8001 python3 /code/test_calendar_crud_proper.py
|
||||
```
|
||||
**After (100%)**:
|
||||
- ✅ CREATE
|
||||
- ✅ READ (조회 성공)
|
||||
- ✅ DELETE (정확한 날짜 표시)
|
||||
|
||||
**Before (67% 통과)**:
|
||||
```
|
||||
✅ PASS CREATE
|
||||
❌ FAIL READ - "11월 17일에 등록된 일정이 없습니다"
|
||||
✅ PASS DELETE
|
||||
통과율: 2/3 (67%)
|
||||
```
|
||||
---
|
||||
|
||||
**After (100% 통과)**:
|
||||
```
|
||||
✅ PASS CREATE
|
||||
✅ PASS READ - "11월 17일 일정: • 15:00 TDD 테스트 일정"
|
||||
✅ PASS DELETE - "TDD 테스트 일정 (2025-11-17 15:00)"
|
||||
통과율: 3/3 (100%)
|
||||
```
|
||||
## 3. 교훈
|
||||
|
||||
### 교훈
|
||||
|
||||
#### 1. Datetime은 항상 timezone-aware로
|
||||
- Python에서 `datetime.now()` 대신 `datetime.now(ZoneInfo("Asia/Seoul"))` 사용
|
||||
- ISO 8601 전송 시 반드시 timezone offset 포함 (`+09:00`)
|
||||
- Google Calendar API는 timezone 정보 없으면 예측 불가능하게 동작
|
||||
|
||||
#### 2. Microservice 간 datetime 전송 규칙
|
||||
### Datetime 규칙
|
||||
- **절대 금지**: naive datetime (timezone 없음)
|
||||
- **권장**: ISO 8601 with timezone (`2025-11-17T15:00:00+09:00`)
|
||||
- **대안**: UTC로 통일 후 각 서비스에서 로컬 변환
|
||||
- **필수**: `datetime.now(ZoneInfo("Asia/Seoul"))`
|
||||
- **전송**: ISO 8601 with timezone (`2025-11-17T15:00:00+09:00`)
|
||||
|
||||
#### 3. 테스트 시 timezone 검증 필수
|
||||
- CREATE/READ/DELETE 모두 같은 timezone 기준 사용 확인
|
||||
- 컨테이너 시스템 timezone과 코드 timezone 일치 여부 확인
|
||||
- `docker exec rb8001 date` → "KST" 확인
|
||||
### Microservice 간 통신
|
||||
- 모든 datetime은 timezone offset 포함
|
||||
- UTC 통일 또는 명시적 timezone 사용
|
||||
- 컨테이너 시스템 timezone 확인: `docker exec [컨테이너] date`
|
||||
|
||||
### 관련 커밋
|
||||
---
|
||||
|
||||
## 4. 관련 커밋
|
||||
- `4cd0990`: fix: Add timezone awareness to calendar operations
|
||||
- `fd7ec1c`: Revert README.md documentation changes
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user