- 📱 PWA 지원: 홈화면 추가 가능한 Progressive Web App - 🎨 M-Project 색상 스키마: 하늘색, 주황색, 회색, 흰색 일관된 디자인 - 📊 대시보드: 데스크톱 캘린더 뷰 + 모바일 일일 뷰 반응형 디자인 - 📥 분류 센터: Gmail 스타일 받은편지함으로 스마트 분류 시스템 - 🤖 AI 분류 제안: 키워드 기반 자동 분류 제안 및 일괄 처리 - 📷 업로드 모달: 데스크톱(파일 선택) + 모바일(카메라/갤러리) 최적화 - 🏷️ 3가지 분류: Todo(시작일), 캘린더(마감일), 체크리스트(무기한) - 📋 체크리스트: 진행률 표시 및 완료 토글 기능 - 🔄 시놀로지 연동 준비: 메일플러스 연동을 위한 구조 설계 - 📱 반응형 UI: 모든 페이지 모바일 최적화 완료
1053 lines
28 KiB
Markdown
1053 lines
28 KiB
Markdown
# 📚 Todo Project 종합 개발 가이드
|
|
|
|
> **"Simple Todo, Smart Integration"**
|
|
> 간결한 할일 관리 + 시놀로지 생태계 연동으로 완벽한 개인 생산성 도구
|
|
|
|
---
|
|
|
|
## 🎯 프로젝트 개요
|
|
|
|
### 목표
|
|
사진과 메모를 기반으로 한 간단한 일정관리 시스템 구축
|
|
|
|
### 핵심 기능
|
|
- **입력**: 사진(선택) + 텍스트 메모
|
|
- **분류**: GTD형 / 캘린더형 / 체크리스트형
|
|
- **플랫폼**: iOS + Apple Watch + 웹
|
|
|
|
### 시스템 환경
|
|
- **배포 서버**: Synology DS1525+
|
|
- **개발 환경**: Mac mini M4 Pro (macOS)
|
|
- **네트워크**: LG U+ 2.5G + Synology Router
|
|
|
|
### 핵심 가치
|
|
- ⚡ **즉시 접근**: 바로 켜서 바로 쓸 수 있는 간편함
|
|
- 🎯 **간결함**: 복잡하지 않은 직관적인 인터페이스
|
|
- 🔗 **스마트 연동**: 시놀로지 캘린더/메일과 자동 동기화
|
|
- 📱 **어디서나**: 폰, 컴퓨터에서 동일한 경험
|
|
|
|
---
|
|
|
|
## 🏗 시스템 아키텍처
|
|
|
|
### 볼륨 매핑 구조
|
|
```
|
|
Synology NAS:
|
|
/volume3/docker/todo-app/ # 시스템/컨테이너
|
|
├── docker-compose.yml
|
|
├── app/ # 애플리케이션 코드
|
|
└── config/ # 설정 파일
|
|
|
|
/volume1/todo-data/ # 데이터 저장
|
|
├── database/ # DB 파일
|
|
├── uploads/images/ # 업로드 이미지
|
|
└── backups/ # 백업
|
|
```
|
|
|
|
### 기술 스택
|
|
```
|
|
Backend: FastAPI (Python) / PostgreSQL
|
|
Frontend: Vanilla JS + Alpine.js + Tailwind CSS
|
|
Database: PostgreSQL (포트: 5434)
|
|
Deployment: Docker + Docker Compose
|
|
Integration: CalDAV, SMTP, DSM API
|
|
```
|
|
|
|
### 전체 구조
|
|
```
|
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
│ Frontend │ │ Backend │ │ Synology │
|
|
│ (4000) │◄──►│ (9000) │◄──►│ Services │
|
|
│ │ │ │ │ │
|
|
│ • PWA Support │ │ • FastAPI │ │ • Calendar │
|
|
│ • Offline Mode │ │ • SQLAlchemy │ │ • MailPlus │
|
|
│ • Auto Login │ │ • PostgreSQL │ │ • DSM API │
|
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
```
|
|
|
|
### API 구조
|
|
```
|
|
POST /api/items # 생성
|
|
GET /api/items?type={type} # 조회
|
|
PUT /api/items/{id} # 수정
|
|
DELETE /api/items/{id} # 삭제
|
|
POST /api/upload/image # 이미지
|
|
```
|
|
|
|
---
|
|
|
|
## 📁 프로젝트 구조
|
|
|
|
```
|
|
Todo-Project/
|
|
├── README.md
|
|
├── DESIGN.md # 설계 문서
|
|
├── SYNOLOGY_INTEGRATION.md # 시놀로지 연동 가이드
|
|
├── docker-compose.yml
|
|
├── .env.example
|
|
├── backend/
|
|
│ ├── Dockerfile
|
|
│ ├── pyproject.toml
|
|
│ ├── src/
|
|
│ │ ├── main.py
|
|
│ │ ├── core/
|
|
│ │ │ ├── config.py
|
|
│ │ │ ├── database.py
|
|
│ │ │ └── security.py
|
|
│ │ ├── models/
|
|
│ │ │ ├── user.py
|
|
│ │ │ └── todo.py
|
|
│ │ ├── schemas/
|
|
│ │ │ ├── auth.py
|
|
│ │ │ └── todo.py
|
|
│ │ ├── api/
|
|
│ │ │ ├── dependencies.py
|
|
│ │ │ └── routes/
|
|
│ │ │ ├── auth.py
|
|
│ │ │ ├── users.py
|
|
│ │ │ └── todos.py
|
|
│ │ └── integrations/
|
|
│ │ ├── synology/
|
|
│ │ │ ├── dsm_auth.py
|
|
│ │ │ ├── calendar_sync.py
|
|
│ │ │ └── mail_service.py
|
|
│ │ └── device_auth.py
|
|
│ └── migrations/
|
|
├── frontend/
|
|
│ ├── index.html
|
|
│ ├── login.html
|
|
│ ├── static/
|
|
│ │ ├── css/
|
|
│ │ │ └── main.css
|
|
│ │ ├── js/
|
|
│ │ │ ├── api.js
|
|
│ │ │ ├── auth.js
|
|
│ │ │ ├── todos.js
|
|
│ │ │ └── synology-sync.js
|
|
│ │ └── icons/
|
|
│ ├── components/
|
|
│ └── manifest.json # PWA 설정
|
|
├── docs/
|
|
│ ├── API.md # API 문서
|
|
│ ├── DEPLOYMENT.md # 배포 가이드
|
|
│ └── SECURITY.md # 보안 가이드
|
|
└── database/
|
|
└── init/
|
|
└── 01_init.sql
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 데이터베이스 설계
|
|
|
|
### ERD (Entity Relationship Diagram)
|
|
```
|
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
│ Users │ │ TodoItems │ │TodoComments │
|
|
├─────────────┤ ├─────────────┤ ├─────────────┤
|
|
│ id (PK) │◄────────│ user_id(FK) │◄────────│todo_item_id │
|
|
│ email │ │ content │ │ content │
|
|
│ password │ │ status │ │ created_at │
|
|
│ full_name │ │ start_date │ └─────────────┘
|
|
│ is_active │ │ estimated │
|
|
│ created_at │ │ completed │
|
|
└─────────────┘ │ parent_id │ ◄─┐
|
|
└─────────────┘ │
|
|
│ │
|
|
└──────────┘
|
|
(Self Reference)
|
|
```
|
|
|
|
### 상태 관리
|
|
```python
|
|
TODO_STATES = {
|
|
"draft": "검토 필요 - 아직 일정 미설정",
|
|
"scheduled": "예정됨 - 일정 설정 완료",
|
|
"active": "진행중 - 현재 작업 중",
|
|
"completed": "완료됨 - 작업 완료",
|
|
"delayed": "지연됨 - 새로운 날짜로 연기"
|
|
}
|
|
```
|
|
|
|
### 데이터 플로우
|
|
```
|
|
1. 할일 생성 → 2. 일정 설정 → 3. 캘린더 동기화 → 4. 메일 알림
|
|
↓ ↓ ↓ ↓
|
|
Draft Scheduled Calendar Email
|
|
Event Sent
|
|
```
|
|
|
|
---
|
|
|
|
## 🔐 보안 설계
|
|
|
|
### 개인용 최적화 보안 모델
|
|
|
|
#### 기기 등록 방식
|
|
```python
|
|
class DeviceAuth:
|
|
"""개인 기기 인증 시스템"""
|
|
|
|
def register_device(self, user_id, device_info):
|
|
"""신뢰할 수 있는 기기 등록"""
|
|
# 기기 고유 ID 생성 (브라우저 fingerprint + 사용자 입력)
|
|
device_id = self.generate_device_id(device_info)
|
|
|
|
# 장기간 유효한 토큰 생성 (30일)
|
|
device_token = self.create_device_token(user_id, device_id)
|
|
|
|
return device_token
|
|
|
|
def quick_login(self, device_token):
|
|
"""기기 토큰으로 빠른 로그인"""
|
|
if self.is_valid_device_token(device_token):
|
|
return self.create_session_token()
|
|
return None
|
|
```
|
|
|
|
#### 보안 레벨
|
|
- **Minimal**: 기기 등록 후 비밀번호 불필요 (개인용 권장)
|
|
- **Balanced**: 주기적 비밀번호 확인
|
|
- **Secure**: 매번 인증 + 생체 인증
|
|
|
|
---
|
|
|
|
## 🔗 시놀로지 연동 설계
|
|
|
|
### 연동 개요
|
|
|
|
Todo-Project는 시놀로지 생태계와 **양방향 연동**되어 **간결한 할일 관리**와 **전체 일정 조망**을 동시에 제공합니다.
|
|
|
|
#### 핵심 철학
|
|
- **Todo-Project**: 빠른 할일 추가 및 관리 (간결함)
|
|
- **시놀로지 캘린더**: 전체 일정 조망 및 통합 관리 (완전성)
|
|
- **MailPlus**: 외부 요청을 Todo로 자동 변환 (자동화)
|
|
|
|
### 🔄 간소화된 연동 플로우 ⭐ 새로운 접근
|
|
|
|
#### 핵심 아이디어: **메일 중심 워크플로우**
|
|
```
|
|
📧 MailPlus 메일 수신
|
|
↓
|
|
📅 시놀로지 캘린더 이벤트 생성 (수동/자동)
|
|
↓
|
|
📋 Todo 자동 추가 (메일 내용 + 첨부파일 포함)
|
|
↓
|
|
🔄 캘린더 ↔ Todo 양방향 동기화
|
|
```
|
|
|
|
#### 장점
|
|
- **단순함**: 복잡한 API 연동 최소화
|
|
- **자연스러움**: 기존 메일 워크플로우 활용
|
|
- **유연성**: 다양한 메일 클라이언트에서 작동
|
|
- **안정성**: 시놀로지 기본 기능 활용
|
|
|
|
### 간소화된 구현 방법
|
|
|
|
#### 1. 메일 모니터링 (핵심 기능)
|
|
```python
|
|
class SimpleMailMonitor:
|
|
"""간단한 메일 모니터링 서비스"""
|
|
|
|
def __init__(self, imap_server, username, password):
|
|
self.imap_server = imap_server
|
|
self.username = username
|
|
self.password = password
|
|
|
|
async def check_new_emails(self):
|
|
"""새 메일 확인 (IMAP)"""
|
|
import imaplib
|
|
import email
|
|
|
|
mail = imaplib.IMAP4_SSL(self.imap_server, 993)
|
|
mail.login(self.username, self.password)
|
|
mail.select('inbox')
|
|
|
|
# 읽지 않은 메일 검색
|
|
status, messages = mail.search(None, 'UNSEEN')
|
|
|
|
new_emails = []
|
|
for msg_id in messages[0].split():
|
|
status, msg_data = mail.fetch(msg_id, '(RFC822)')
|
|
email_body = msg_data[0][1]
|
|
email_message = email.message_from_bytes(email_body)
|
|
|
|
new_emails.append({
|
|
'id': msg_id,
|
|
'subject': email_message['Subject'],
|
|
'from': email_message['From'],
|
|
'body': self._extract_body(email_message),
|
|
'attachments': self._extract_attachments(email_message)
|
|
})
|
|
|
|
mail.close()
|
|
mail.logout()
|
|
return new_emails
|
|
```
|
|
|
|
#### 2. 메일 → Todo 자동 변환 서비스
|
|
```python
|
|
class MailToTodoService:
|
|
"""메일을 Todo로 자동 변환하는 간단한 서비스"""
|
|
|
|
def __init__(self, mail_monitor, todo_service):
|
|
self.mail_monitor = mail_monitor
|
|
self.todo_service = todo_service
|
|
|
|
async def monitor_emails(self):
|
|
"""메일 모니터링 및 자동 변환"""
|
|
while True:
|
|
try:
|
|
new_emails = await self.mail_auth.check_new_emails()
|
|
|
|
for email in new_emails:
|
|
if self._is_todo_email(email):
|
|
todo_item = await self._convert_email_to_todo(email)
|
|
await self.todo_service.create_todo(todo_item)
|
|
|
|
# 캘린더에도 동기화
|
|
await self.calendar_sync.sync_todo_to_calendar(todo_item)
|
|
|
|
except Exception as e:
|
|
logger.error(f"메일 모니터링 오류: {e}")
|
|
|
|
await asyncio.sleep(60) # 1분마다 체크
|
|
|
|
def _is_todo_email(self, email):
|
|
"""Todo 변환 대상 메일인지 판단"""
|
|
# 제목에 특정 키워드 포함
|
|
todo_keywords = ['할일', 'TODO', 'Task', '작업', '요청']
|
|
subject = email['subject'].lower()
|
|
|
|
return any(keyword.lower() in subject for keyword in todo_keywords)
|
|
|
|
async def _convert_email_to_todo(self, email):
|
|
"""메일을 Todo 항목으로 변환"""
|
|
# 제목에서 할일 내용 추출
|
|
content = self._extract_todo_content(email['subject'])
|
|
|
|
# 본문에서 날짜/시간 추출
|
|
due_date = self._extract_date_from_body(email['body'])
|
|
|
|
# 첨부파일 처리
|
|
attachments = await self._save_attachments(email['attachments'])
|
|
|
|
todo_data = {
|
|
'content': content,
|
|
'description': email['body'],
|
|
'due_date': due_date,
|
|
'attachments': attachments,
|
|
'source': 'email',
|
|
'source_id': email['id'],
|
|
'sender': email['from']
|
|
}
|
|
|
|
return todo_data
|
|
|
|
def _extract_todo_content(self, subject):
|
|
"""제목에서 할일 내용 추출"""
|
|
# "할일: 문서 검토" → "문서 검토"
|
|
# "TODO: Review document" → "Review document"
|
|
import re
|
|
|
|
patterns = [
|
|
r'할일:\s*(.+)',
|
|
r'TODO:\s*(.+)',
|
|
r'Task:\s*(.+)',
|
|
r'작업:\s*(.+)',
|
|
r'요청:\s*(.+)'
|
|
]
|
|
|
|
for pattern in patterns:
|
|
match = re.search(pattern, subject, re.IGNORECASE)
|
|
if match:
|
|
return match.group(1).strip()
|
|
|
|
return subject # 패턴이 없으면 전체 제목 사용
|
|
|
|
def _extract_date_from_body(self, body):
|
|
"""본문에서 날짜 추출"""
|
|
import re
|
|
from datetime import datetime, timedelta
|
|
|
|
# 날짜 패턴들
|
|
date_patterns = [
|
|
r'(\d{4}-\d{2}-\d{2})', # 2024-01-15
|
|
r'(\d{2}/\d{2}/\d{4})', # 01/15/2024
|
|
r'(\d{1,2}월\s*\d{1,2}일)', # 1월 15일
|
|
r'(내일|tomorrow)',
|
|
r'(다음주|next week)',
|
|
r'(이번주|this week)'
|
|
]
|
|
|
|
for pattern in date_patterns:
|
|
match = re.search(pattern, body, re.IGNORECASE)
|
|
if match:
|
|
date_str = match.group(1)
|
|
return self._parse_date_string(date_str)
|
|
|
|
# 기본값: 3일 후
|
|
return datetime.now() + timedelta(days=3)
|
|
|
|
async def _save_attachments(self, attachments):
|
|
"""첨부파일을 NAS에 저장"""
|
|
saved_files = []
|
|
|
|
for attachment in attachments:
|
|
# /volume1/todo-data/attachments/ 에 저장
|
|
file_path = f"/data/attachments/{attachment['filename']}"
|
|
|
|
with open(file_path, 'wb') as f:
|
|
f.write(attachment['content'])
|
|
|
|
saved_files.append({
|
|
'filename': attachment['filename'],
|
|
'path': file_path,
|
|
'size': len(attachment['content'])
|
|
})
|
|
|
|
return saved_files
|
|
```
|
|
|
|
### ⚙️ 환경 설정
|
|
|
|
#### 환경 변수 설정
|
|
```bash
|
|
# .env 파일
|
|
# 시놀로지 메일 설정 (필수)
|
|
SYNOLOGY_MAIL_SERVER=your-nas.synology.me
|
|
SYNOLOGY_MAIL_USERNAME=todo_user
|
|
SYNOLOGY_MAIL_PASSWORD=your_secure_password
|
|
|
|
# 메일 모니터링 설정
|
|
ENABLE_MAIL_MONITORING=true
|
|
MAIL_CHECK_INTERVAL=60 # 초 단위
|
|
TODO_KEYWORDS=할일,TODO,Task,작업,요청
|
|
|
|
# 첨부파일 저장 경로
|
|
ATTACHMENTS_PATH=/data/attachments
|
|
```
|
|
|
|
#### 시놀로지 NAS 설정
|
|
|
|
##### 1. MailPlus 설정
|
|
```bash
|
|
# MailPlus 패키지에서:
|
|
1. IMAP 활성화: 설정 → IMAP/POP3 → IMAP 활성화
|
|
2. 포트: 993 (SSL) 또는 143 (일반)
|
|
3. 전용 계정 생성: todo_mail_user
|
|
4. 권한: MailPlus 접근만 허용
|
|
```
|
|
|
|
##### 2. 캘린더 설정 (선택사항)
|
|
```bash
|
|
# Calendar 패키지에서:
|
|
1. 새 캘린더 생성: "Todo Tasks"
|
|
2. 색상: 보라색 (#6366f1)
|
|
3. 메일에서 일정 추가 기능 활성화
|
|
```
|
|
|
|
### 🔧 사용 방법
|
|
|
|
#### 1. 메일로 할일 추가
|
|
```
|
|
제목: 할일: 프로젝트 문서 검토
|
|
본문:
|
|
- 마감일: 2024-01-20
|
|
- 예상 소요시간: 2시간
|
|
- 첨부파일: project_spec.pdf
|
|
|
|
→ Todo 앱에 자동으로 추가됨
|
|
```
|
|
|
|
#### 2. 캘린더 연동 (선택)
|
|
```
|
|
MailPlus → 캘린더 이벤트 생성 → Todo 동기화
|
|
캘린더에서 완료 처리 → Todo 상태 자동 업데이트
|
|
```
|
|
|
|
#### 3. 첨부파일 관리
|
|
```
|
|
메일 첨부파일 → NAS 저장 (/data/attachments/)
|
|
Todo에서 파일 링크로 접근 가능
|
|
```
|
|
|
|
---
|
|
|
|
## 📱 프론트엔드 설계
|
|
|
|
### PWA (Progressive Web App) 구조
|
|
```javascript
|
|
// manifest.json
|
|
{
|
|
"name": "Todo Project",
|
|
"short_name": "Todo",
|
|
"start_url": "/",
|
|
"display": "standalone",
|
|
"theme_color": "#6366f1",
|
|
"shortcuts": [
|
|
{
|
|
"name": "빠른 할일 추가",
|
|
"url": "/quick-add"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### 오프라인 지원
|
|
```javascript
|
|
// Service Worker
|
|
self.addEventListener('fetch', event => {
|
|
if (event.request.url.includes('/api/todos')) {
|
|
event.respondWith(
|
|
// 온라인: API 호출
|
|
// 오프라인: 로컬 캐시 사용
|
|
caches.match(event.request) || fetch(event.request)
|
|
);
|
|
}
|
|
});
|
|
```
|
|
|
|
### 상태 관리
|
|
```javascript
|
|
class TodoState {
|
|
constructor() {
|
|
this.todos = [];
|
|
this.syncQueue = []; // 오프라인 시 동기화 대기열
|
|
this.isOnline = navigator.onLine;
|
|
}
|
|
|
|
async addTodo(content) {
|
|
const todo = this.createTodo(content);
|
|
|
|
if (this.isOnline) {
|
|
await this.syncToServer(todo);
|
|
} else {
|
|
this.syncQueue.push({action: 'create', todo});
|
|
}
|
|
|
|
return todo;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 간결한 인터페이스 원칙
|
|
|
|
#### 메인 화면
|
|
```html
|
|
<!-- 최소한의 요소만 표시 -->
|
|
<div class="todo-app">
|
|
<!-- 빠른 입력 -->
|
|
<input class="quick-add" placeholder="할일을 입력하세요..." />
|
|
|
|
<!-- 간단한 탭 -->
|
|
<div class="tabs">
|
|
<button class="tab active">진행중</button>
|
|
<button class="tab">완료</button>
|
|
</div>
|
|
|
|
<!-- 할일 목록 -->
|
|
<div class="todo-list">
|
|
<!-- 할일 아이템들 -->
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
#### 모바일 최적화
|
|
- **터치 친화적**: 44px 이상 터치 영역
|
|
- **햅틱 피드백**: 액션 시 진동 피드백
|
|
- **풀투리프레시**: 아래로 당겨서 새로고침
|
|
- **오프라인 표시**: 네트워크 상태 표시
|
|
|
|
### 색상 시스템
|
|
```css
|
|
:root {
|
|
--primary: #6366f1; /* 보라색 - 메인 컬러 */
|
|
--success: #10b981; /* 초록색 - 완료 */
|
|
--warning: #f59e0b; /* 주황색 - 진행중 */
|
|
--danger: #ef4444; /* 빨간색 - 지연 */
|
|
--gray: #6b7280; /* 회색 - 대기 */
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🎯 코딩 표준 및 규칙
|
|
|
|
### 핵심 원칙: "간결함 (Simplicity)"
|
|
|
|
> **"복잡함은 버그의 온상이다. 간결함이 최고의 아름다움이다."**
|
|
|
|
### 파일 크기 제한
|
|
|
|
#### 핵심 로직 파일 (엄격)
|
|
- **모델 파일 (models/)**: 최대 200줄
|
|
- **스키마 파일 (schemas/)**: 최대 150줄
|
|
- **설정 파일 (config.py)**: 최대 100줄
|
|
|
|
#### 서비스 파일 (보통)
|
|
- **API 라우터**: 최대 400줄
|
|
- **서비스 클래스**: 최대 350줄
|
|
- **통합 서비스**: 최대 500줄
|
|
|
|
#### 프론트엔드 파일 (유연)
|
|
- **JavaScript 파일**: 최대 300줄
|
|
- **HTML 파일**: 최대 250줄
|
|
- **CSS 파일**: 최대 200줄
|
|
|
|
### 함수/메서드 크기 제한
|
|
- **단순 함수**: 최대 20줄
|
|
- **비즈니스 로직 함수**: 최대 40줄
|
|
- **통합/조합 함수**: 최대 60줄
|
|
- **초기화 함수**: 최대 30줄
|
|
|
|
### 간결성 규칙
|
|
|
|
#### 1. 한 가지 일만 하기
|
|
```python
|
|
# ❌ 나쁜 예 - 여러 일을 함
|
|
def process_todo_and_send_email_and_update_calendar(todo):
|
|
# 할일 처리
|
|
# 이메일 발송
|
|
# 캘린더 업데이트
|
|
pass
|
|
|
|
# ✅ 좋은 예 - 각각 분리
|
|
def process_todo(todo): pass
|
|
def send_notification_email(todo): pass
|
|
def update_calendar(todo): pass
|
|
```
|
|
|
|
#### 2. 명확한 이름 사용
|
|
```python
|
|
# ❌ 나쁜 예
|
|
def calc(x, y, z): pass
|
|
def proc_data(d): pass
|
|
|
|
# ✅ 좋은 예
|
|
def calculate_estimated_time(start, duration, buffer): pass
|
|
def process_todo_item(todo_data): pass
|
|
```
|
|
|
|
#### 3. 중복 제거
|
|
```python
|
|
# ❌ 나쁜 예 - 중복 코드
|
|
def create_synology_event(todo):
|
|
event = CalendarEvent()
|
|
event.title = f"📋 {todo.content}"
|
|
event.start_time = todo.start_date
|
|
# ... 공통 로직
|
|
|
|
# ✅ 좋은 예 - 공통 로직 분리
|
|
def create_base_event(todo):
|
|
return CalendarEvent(
|
|
title=f"📋 {todo.content}",
|
|
start_time=todo.start_date
|
|
)
|
|
|
|
def create_synology_event(todo):
|
|
event = create_base_event(todo)
|
|
# 시놀로지 특화 로직만
|
|
return event
|
|
```
|
|
|
|
---
|
|
|
|
## 🐳 Docker 설정
|
|
|
|
### docker-compose.yml (Production - NAS)
|
|
```yaml
|
|
version: '3.8'
|
|
|
|
services:
|
|
frontend:
|
|
build: ./frontend
|
|
container_name: todo-web
|
|
ports:
|
|
- "4000:80"
|
|
depends_on:
|
|
- backend
|
|
restart: unless-stopped
|
|
|
|
backend:
|
|
build: ./backend
|
|
container_name: todo-api
|
|
ports:
|
|
- "9000:9000"
|
|
volumes:
|
|
- /volume3/docker/todo-app/app:/app
|
|
- /volume1/todo-data:/data
|
|
environment:
|
|
- DATABASE_URL=postgresql://todouser:${DB_PASSWORD}@database:5432/todo
|
|
- UPLOAD_PATH=/data/uploads/images
|
|
- TZ=Asia/Seoul
|
|
depends_on:
|
|
- database
|
|
restart: unless-stopped
|
|
|
|
database:
|
|
image: postgres:15-alpine
|
|
container_name: todo-db
|
|
ports:
|
|
- "5434:5432"
|
|
volumes:
|
|
- /volume1/todo-data/database:/var/lib/postgresql/data
|
|
environment:
|
|
- POSTGRES_DB=todo
|
|
- POSTGRES_USER=todouser
|
|
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
|
restart: unless-stopped
|
|
```
|
|
|
|
---
|
|
|
|
## 🔄 개발 워크플로우
|
|
|
|
### Mac 로컬 개발 구조
|
|
```bash
|
|
~/Developer/todo-app/
|
|
├── backend/
|
|
│ ├── main.py
|
|
│ ├── requirements.txt
|
|
│ ├── Dockerfile
|
|
│ └── docker-compose.dev.yml
|
|
├── frontend/
|
|
│ └── (React/Vue 파일)
|
|
├── ios/
|
|
│ └── TodoApp.xcodeproj
|
|
└── README.md
|
|
```
|
|
|
|
### 1단계: 로컬 개발 (Mac)
|
|
```bash
|
|
# 프로젝트 시작
|
|
cd ~/Developer/todo-app
|
|
docker-compose -f docker-compose.dev.yml up
|
|
|
|
# API 테스트
|
|
curl http://localhost:9000/api/items
|
|
|
|
# 프론트엔드 개발
|
|
npm run dev # http://localhost:4000
|
|
```
|
|
|
|
### 2단계: NAS 배포
|
|
```bash
|
|
# Mac에서 푸시
|
|
git add .
|
|
git commit -m "기능 추가"
|
|
git push gitea main
|
|
|
|
# NAS SSH 접속
|
|
ssh admin@nas-ip
|
|
|
|
# 배포
|
|
cd /volume3/docker/todo-app
|
|
git pull
|
|
docker-compose down
|
|
docker-compose up -d --build
|
|
```
|
|
|
|
### 3단계: iOS 앱 연동
|
|
```swift
|
|
// Development
|
|
let API_BASE = "http://192.168.1.100:9000"
|
|
|
|
// Production
|
|
let API_BASE = "https://your.synology.me:9000"
|
|
```
|
|
|
|
---
|
|
|
|
## 🚀 빠른 시작
|
|
|
|
### 포트 설정
|
|
- **Frontend**: http://localhost:4000
|
|
- **Backend API**: http://localhost:9000
|
|
- **Database**: localhost:5434
|
|
|
|
### 개발 환경 설정
|
|
|
|
1. **환경 변수 설정**
|
|
```bash
|
|
cp .env.example .env
|
|
# .env 파일에서 시놀로지 연동 정보 설정 (선택사항)
|
|
```
|
|
|
|
2. **Docker로 실행**
|
|
```bash
|
|
docker-compose up -d
|
|
```
|
|
|
|
3. **브라우저에서 접속**
|
|
```
|
|
http://localhost:4000
|
|
```
|
|
|
|
4. **개인용 설정 (권장)**
|
|
- 첫 접속 시 "개인용으로 설정" 선택
|
|
- 내 기기 등록으로 자동 로그인 활성화
|
|
|
|
### 초기 설정 명령어
|
|
```bash
|
|
# Mac: 프로젝트 생성
|
|
mkdir -p ~/Developer/todo-app/{backend,frontend,ios}
|
|
cd ~/Developer/todo-app
|
|
|
|
# NAS: 폴더 생성
|
|
ssh admin@nas-ip
|
|
sudo mkdir -p /volume3/docker/todo-app
|
|
sudo mkdir -p /volume1/todo-data/{database,uploads/images,backups}
|
|
|
|
# Git 초기화
|
|
git init
|
|
git remote add origin http://nas-ip:3000/username/todo-app.git
|
|
```
|
|
|
|
### 첫 배포 테스트
|
|
```bash
|
|
# 1. 간단한 API 작성
|
|
echo "from fastapi import FastAPI
|
|
app = FastAPI()
|
|
@app.get('/')
|
|
def read_root():
|
|
return {'status': 'ok'}" > backend/main.py
|
|
|
|
# 2. Dockerfile 생성
|
|
echo "FROM python:3.11-slim
|
|
WORKDIR /app
|
|
RUN pip install fastapi uvicorn
|
|
COPY . .
|
|
CMD ['uvicorn', 'main:app', '--host', '0.0.0.0', '--port', '9000']" > backend/Dockerfile
|
|
|
|
# 3. 배포 및 테스트
|
|
docker build -t todo-api backend/
|
|
docker run -p 9000:9000 todo-api
|
|
curl http://localhost:9000
|
|
```
|
|
|
|
---
|
|
|
|
## 📝 구현 로드맵
|
|
|
|
### Phase 1: 백엔드 (Week 1)
|
|
- [ ] Docker 환경 구성
|
|
- [ ] FastAPI 기본 CRUD
|
|
- [ ] PostgreSQL 스키마
|
|
- [ ] 이미지 업로드 처리
|
|
- [ ] JWT 인증
|
|
|
|
### Phase 2: 웹 프론트엔드 (Week 1)
|
|
- [ ] React/Vue 프로젝트 설정
|
|
- [ ] 3가지 분류 UI
|
|
- [ ] 이미지 업로드 컴포넌트
|
|
- [ ] PWA 설정
|
|
- [ ] 반응형 디자인
|
|
|
|
### Phase 3: iOS 앱 (Week 2-3)
|
|
- [ ] SwiftUI 기본 구조
|
|
- [ ] API Service 클래스
|
|
- [ ] 카메라/갤러리 연동
|
|
- [ ] 오프라인 캐싱
|
|
- [ ] 푸시 알림
|
|
|
|
### Phase 4: Apple Watch (Week 1)
|
|
- [ ] WatchOS 타겟 추가
|
|
- [ ] 오늘 할일 뷰
|
|
- [ ] 체크 기능
|
|
- [ ] 컴플리케이션
|
|
|
|
### 시놀로지 연동 (1단계)
|
|
- [ ] 시놀로지 인증 시스템
|
|
- [ ] 시놀로지 캘린더 연동
|
|
- [ ] PWA 모바일 지원
|
|
- [ ] 브라우저 확장 프로그램
|
|
- [ ] 오프라인 기능
|
|
|
|
---
|
|
|
|
## 🔧 NAS 설정 체크리스트
|
|
|
|
### Container Manager
|
|
```
|
|
1. 프로젝트 생성
|
|
- 이름: todo-app
|
|
- 경로: /volume3/docker/todo-app
|
|
|
|
2. 포트 설정
|
|
- Frontend: 4000
|
|
- Backend: 9000
|
|
- Database: 5434
|
|
|
|
3. 볼륨 마운트
|
|
- 소스: /volume1/todo-data
|
|
- 타겟: /data
|
|
```
|
|
|
|
### 리버스 프록시 (선택)
|
|
```
|
|
제어판 > 로그인 포털 > 고급 > 리버스 프록시
|
|
- 소스: https://todo.your-domain.me
|
|
- 대상: http://localhost:4000
|
|
- HSTS, HTTP/2 활성화
|
|
```
|
|
|
|
---
|
|
|
|
## 🧪 테스트 전략
|
|
|
|
### 테스트 피라미드
|
|
```
|
|
┌─────────────┐
|
|
│ E2E Tests │ ← 사용자 시나리오
|
|
├─────────────┤
|
|
│ Integration │ ← API + DB 통합
|
|
│ Tests │
|
|
├─────────────┤
|
|
│ Unit Tests │ ← 개별 함수/클래스
|
|
└─────────────┘
|
|
```
|
|
|
|
### 중요 테스트 케이스
|
|
- **할일 CRUD**: 생성, 조회, 수정, 삭제
|
|
- **상태 전환**: Draft → Active → Completed
|
|
- **시놀로지 연동**: 캘린더 동기화, 메일 발송
|
|
- **오프라인 모드**: 네트워크 없이 기본 기능
|
|
- **기기 인증**: 등록, 로그인, 토큰 갱신
|
|
|
|
---
|
|
|
|
## 🛠 도구 및 자동화
|
|
|
|
### 코드 품질 도구
|
|
```bash
|
|
# Python
|
|
pip install flake8 black isort
|
|
|
|
# 라인 수 체크
|
|
find . -name "*.py" -exec wc -l {} + | sort -n
|
|
|
|
# 복잡도 체크
|
|
pip install mccabe
|
|
flake8 --max-complexity=5 .
|
|
```
|
|
|
|
### pre-commit 훅
|
|
```yaml
|
|
# .pre-commit-config.yaml
|
|
repos:
|
|
- repo: local
|
|
hooks:
|
|
- id: check-file-size
|
|
name: Check file size
|
|
entry: bash -c 'find . -name "*.py" -exec wc -l {} + | awk "$1 > 300 {print $2 " exceeds 300 lines (" $1 ")"; exit 1}"'
|
|
language: system
|
|
```
|
|
|
|
---
|
|
|
|
## 📈 성능 최적화
|
|
|
|
### 프론트엔드 최적화
|
|
- **지연 로딩**: 필요한 컴포넌트만 로드
|
|
- **가상 스크롤**: 많은 할일 목록 처리
|
|
- **캐싱**: 자주 사용하는 데이터 캐시
|
|
- **압축**: 정적 파일 gzip 압축
|
|
|
|
### 백엔드 최적화
|
|
- **데이터베이스 인덱스**: 자주 조회하는 컬럼
|
|
- **연결 풀링**: 데이터베이스 연결 재사용
|
|
- **캐싱**: Redis를 통한 세션 캐시
|
|
- **비동기 처리**: FastAPI async/await 활용
|
|
|
|
---
|
|
|
|
## 🔄 동기화 전략
|
|
|
|
### 실시간 동기화
|
|
```python
|
|
class SyncManager:
|
|
"""동기화 관리자"""
|
|
|
|
async def sync_todo_change(self, todo_id, action):
|
|
"""할일 변경 시 동기화"""
|
|
|
|
# 1. 로컬 상태 업데이트
|
|
await self.update_local_state(todo_id, action)
|
|
|
|
# 2. 시놀로지 캘린더 동기화
|
|
if self.synology_enabled:
|
|
await self.sync_to_calendar(todo_id, action)
|
|
|
|
# 3. 다른 기기에 알림 (WebSocket)
|
|
await self.notify_other_devices(todo_id, action)
|
|
```
|
|
|
|
### 충돌 해결
|
|
- **Last Write Wins**: 마지막 수정이 우선
|
|
- **사용자 선택**: 충돌 시 사용자가 선택
|
|
- **자동 병합**: 단순한 변경사항은 자동 병합
|
|
|
|
---
|
|
|
|
## 📌 참고사항
|
|
|
|
### 보안
|
|
- JWT 토큰 구현 필수
|
|
- HTTPS 설정 (Let's Encrypt)
|
|
- 환경변수로 민감정보 관리
|
|
|
|
### 백업
|
|
- Hyper Backup으로 자동 백업 설정
|
|
- `/volume1/todo-data` 전체 백업
|
|
- Git으로 코드 버전 관리
|
|
|
|
### 모니터링
|
|
- Synology Resource Monitor 활용
|
|
- Docker 로그: `docker logs todo-api`
|
|
- 에러 추적: Sentry 고려
|
|
|
|
---
|
|
|
|
## 📋 체크리스트
|
|
|
|
### 코드 작성 전
|
|
- [ ] 이 기능이 정말 필요한가?
|
|
- [ ] 더 간단한 방법은 없는가?
|
|
- [ ] 기존 코드를 재사용할 수 있는가?
|
|
|
|
### 코드 작성 중
|
|
- [ ] 함수가 30줄을 넘지 않는가?
|
|
- [ ] 하나의 함수가 한 가지 일만 하는가?
|
|
- [ ] 변수명이 명확한가?
|
|
|
|
### 코드 작성 후
|
|
- [ ] 파일이 300줄을 넘지 않는가?
|
|
- [ ] 중복 코드가 있는가?
|
|
- [ ] 다른 개발자가 쉽게 이해할 수 있는가?
|
|
|
|
---
|
|
|
|
## 🎯 1단계 목표 (시놀로지 연동)
|
|
|
|
1. **할일 → 캘린더**: 일정 설정 시 시놀로지 캘린더에 'todo' 태그로 등록
|
|
2. **메일 알림**: MailPlus를 통한 검토 정보 발송
|
|
3. **상태 동기화**: 완료 시 '완료' 태그로 변경
|
|
4. **지연 처리**: 지연 시 캘린더 날짜 자동 수정
|
|
|
|
---
|
|
|
|
## 🤝 기여 방법
|
|
|
|
이 프로젝트는 개인용으로 시작되었지만, 유용한 기능이나 개선사항이 있다면 언제든 기여해주세요!
|
|
|
|
---
|
|
|
|
## 📄 라이선스
|
|
|
|
이 프로젝트는 기존 Document Server 프로젝트에서 분리되어 독립적으로 개발되고 있습니다.
|
|
|
|
---
|
|
|
|
**이 종합 가이드를 따라가면 체계적이고 간결한 Todo 앱을 개발할 수 있습니다! 🚀**
|