# πŸ“š 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
``` #### λͺ¨λ°”일 μ΅œμ ν™” - **ν„°μΉ˜ μΉœν™”μ **: 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 앱을 κ°œλ°œν•  수 μžˆμŠ΅λ‹ˆλ‹€! πŸš€**