문서 파일 정리
This commit is contained in:
227
docs/02_기술_문서/01_시스템_아키텍처.md
Normal file
227
docs/02_기술_문서/01_시스템_아키텍처.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# 적자 탈출 가계부 시스템 아키텍처
|
||||
|
||||
## 시스템 개요
|
||||
|
||||
'적자 탈출 가계부' 애플리케이션은 사용자의 재정 데이터를 안전하게 관리하고, 효과적인 분석을 제공하며, 다양한 기기에서 일관된 사용자 경험을 제공하기 위한 클라이언트-서버 아키텍처를 채택합니다. 본 문서는 시스템의 전반적인 아키텍처와 주요 구성 요소를 설명합니다.
|
||||
|
||||
## 아키텍처 다이어그램
|
||||
|
||||
```
|
||||
+---------------------------+ +---------------------------+
|
||||
| | | |
|
||||
| 클라이언트 애플리케이션 | | 백엔드 서버 |
|
||||
| | | |
|
||||
| +---------------------+ | | +---------------------+ |
|
||||
| | 프레젠테이션 계층 | | | | API 계층 | |
|
||||
| | | | | | | |
|
||||
| | - React UI | | | | - RESTful API | |
|
||||
| | - Tailwind CSS | | | | - GraphQL API | |
|
||||
| | - Capacitor | | | | - 인증/인가 | |
|
||||
| +---------------------+ | | +---------------------+ |
|
||||
| | | | | |
|
||||
| +---------------------+ | | +---------------------+ |
|
||||
| | 비즈니스 로직 계층 | | | | 서비스 계층 | |
|
||||
| | | | | | | |
|
||||
| | - 데이터 검증 | | | | - 비즈니스 로직 | |
|
||||
| | - 상태 관리 | | | | - 데이터 처리 | |
|
||||
| | - API 통신 | | | | - 외부 서비스 통합 | |
|
||||
| +---------------------+ | | +---------------------+ |
|
||||
| | | | | |
|
||||
| +---------------------+ | | +---------------------+ |
|
||||
| | 데이터 계층 | | | | 데이터 접근 계층 | |
|
||||
| | | | | | | |
|
||||
| | - 웹 스토리지 | | | | - ORM | |
|
||||
| | - IndexedDB | | | | - 데이터베이스 연결 | |
|
||||
| | - 데이터 동기화 | | | | - 캐싱 | |
|
||||
| +---------------------+ | | +---------------------+ |
|
||||
| | | | | |
|
||||
| +---------------------+ | | +---------------------+ |
|
||||
| | 네이티브 계층 | | | | 데이터베이스 | |
|
||||
| | | | | | | |
|
||||
| | - Capacitor 플러그인 | | | | - Supabase(PostgreSQL) | |
|
||||
| | - 네이티브 API 접근 | | | | - Redis 캐시 | |
|
||||
| +---------------------+ | | +---------------------+ |
|
||||
| | | |
|
||||
+---------------------------+ +---------------------------+
|
||||
|
|
||||
+---------------------------+
|
||||
| |
|
||||
| 외부 서비스 통합 |
|
||||
| |
|
||||
| - 금융 기관 API |
|
||||
| - 영수증 OCR 서비스 |
|
||||
| - 푸시 알림 서비스 |
|
||||
| - 클라우드 스토리지 |
|
||||
| - AI/ML 분석 서비스 |
|
||||
| |
|
||||
+---------------------------+
|
||||
```
|
||||
|
||||
## 주요 구성 요소
|
||||
|
||||
### 1. 클라이언트 애플리케이션
|
||||
|
||||
#### 프레젠테이션 계층
|
||||
- **기술 스택**: React, Vite, Tailwind CSS, Capacitor
|
||||
- **주요 기능**:
|
||||
- 사용자 인터페이스 렌더링
|
||||
- 사용자 입력 처리
|
||||
- 화면 전환 및 네비게이션
|
||||
- 반응형 디자인 구현
|
||||
- 뉴모피즘 스타일 UI 컴포넌트
|
||||
|
||||
#### 비즈니스 로직 계층
|
||||
- **기술 스택**: JavaScript/TypeScript, React Hooks
|
||||
- **주요 기능**:
|
||||
- 클라이언트 측 데이터 검증
|
||||
- 상태 관리 및 업데이트
|
||||
- API 요청 처리
|
||||
- 오프라인 작업 관리
|
||||
|
||||
#### 데이터 계층
|
||||
- **기술 스택**: LocalStorage, IndexedDB, Capacitor Storage API
|
||||
- **주요 기능**:
|
||||
- 로컬 데이터 저장 및 관리
|
||||
- 서버와의 데이터 동기화
|
||||
- 오프라인 캐싱
|
||||
- 민감 데이터 암호화
|
||||
|
||||
#### 네이티브 계층
|
||||
- **기술 스택**: Capacitor, 네이티브 플러그인
|
||||
- **주요 기능**:
|
||||
- 네이티브 기기 기능 접근 (카메라, 파일 시스템 등)
|
||||
- 푸시 알림 처리
|
||||
- 앱 생명주기 관리
|
||||
- 네이티브 UI 요소 통합
|
||||
|
||||
### 2. 백엔드 서버
|
||||
|
||||
#### API 계층
|
||||
- **기술 스택**: Node.js, Express, Apollo Server
|
||||
- **주요 기능**:
|
||||
- RESTful API 엔드포인트 제공
|
||||
- GraphQL API 제공 (고급 쿼리용)
|
||||
- 인증 및 인가 처리
|
||||
- 요청 검증 및 응답 포맷팅
|
||||
|
||||
#### 서비스 계층
|
||||
- **기술 스택**: Node.js, TypeScript
|
||||
- **주요 기능**:
|
||||
- 핵심 비즈니스 로직 구현
|
||||
- 데이터 처리 및 변환
|
||||
- 외부 서비스 통합
|
||||
- 트랜잭션 관리
|
||||
|
||||
#### 데이터 접근 계층
|
||||
- **기술 스택**: Supabase SDK, Redis
|
||||
- **주요 기능**:
|
||||
- 데이터베이스 연결 관리
|
||||
- 쿼리 최적화
|
||||
- 데이터 캐싱
|
||||
- 데이터 무결성 보장
|
||||
|
||||
#### 데이터베이스
|
||||
- **기술 스택**: Supabase(PostgreSQL), Redis
|
||||
- **주요 기능**:
|
||||
- 사용자 데이터 영구 저장
|
||||
- 트랜잭션 데이터 관리
|
||||
- 분석 데이터 저장
|
||||
- 세션 및 캐시 데이터 관리
|
||||
- 실시간 데이터 구독 및 웨브소켓 기반 업데이트
|
||||
- Row-level 보안 정책 적용
|
||||
- PostgreSQL 확장 기능 활용
|
||||
|
||||
### 3. 외부 서비스 통합
|
||||
|
||||
- **금융 기관 API**: 은행 계좌 및 카드 거래 데이터 연동
|
||||
- **영수증 OCR 서비스**: 영수증 이미지에서 텍스트 및 금액 추출
|
||||
- **푸시 알림 서비스**: Firebase Cloud Messaging 또는 Apple Push Notification Service
|
||||
- **클라우드 스토리지**: AWS S3 또는 Firebase Storage
|
||||
- **AI/ML 분석 서비스**: TensorFlow 기반 소비 패턴 분석 및 예측
|
||||
|
||||
## 데이터 흐름
|
||||
|
||||
### 1. 사용자 인증 흐름
|
||||
1. 사용자가 앱에 로그인 정보 입력
|
||||
2. 클라이언트가 인증 요청을 백엔드 서버로 전송
|
||||
3. 서버가 인증 정보 검증 후 JWT 토큰 발급
|
||||
4. 클라이언트가 토큰을 저장하고 이후 요청에 포함
|
||||
5. 토큰 만료 시 자동 갱신 또는 재로그인 요청
|
||||
|
||||
### 2. 데이터 동기화 흐름
|
||||
1. 사용자가 오프라인 상태에서 데이터 입력
|
||||
2. 데이터가 로컬 데이터베이스에 저장
|
||||
3. 네트워크 연결 시 자동 동기화 시작
|
||||
4. 서버에서 충돌 감지 및 해결
|
||||
5. 최신 데이터로 클라이언트 상태 업데이트
|
||||
|
||||
### 3. 분석 데이터 흐름
|
||||
1. 사용자 거래 데이터가 서버에 저장
|
||||
2. 배치 프로세스가 정기적으로 데이터 분석 수행
|
||||
3. 분석 결과가 데이터베이스에 저장
|
||||
4. 사용자가 분석 화면 접근 시 데이터 요청
|
||||
5. 서버가 분석 결과 반환 및 클라이언트 렌더링
|
||||
|
||||
## 보안 아키텍처
|
||||
|
||||
### 데이터 보안
|
||||
- **전송 중 데이터**: TLS/SSL 암호화
|
||||
- **저장 데이터**: AES-256 암호화
|
||||
- **민감 정보**: 토큰화 및 해싱 처리
|
||||
- **데이터베이스**: 접근 제어 및 암호화
|
||||
|
||||
### 인증 및 인가
|
||||
- **사용자 인증**: JWT 기반 인증
|
||||
- **소셜 로그인**: OAuth 2.0 통합
|
||||
- **다중 인증**: 2FA 옵션 제공
|
||||
- **권한 관리**: RBAC(Role-Based Access Control)
|
||||
|
||||
### 보안 모니터링
|
||||
- **로깅**: 모든 API 요청 및 중요 이벤트 로깅
|
||||
- **모니터링**: 실시간 보안 이벤트 모니터링
|
||||
- **알림**: 의심스러운 활동 감지 시 알림
|
||||
- **감사**: 정기적인 보안 감사 및 취약점 스캔
|
||||
|
||||
## 확장성 및 성능
|
||||
|
||||
### 수평적 확장
|
||||
- **서버리스 아키텍처**: AWS Lambda 또는 Firebase Functions
|
||||
- **컨테이너화**: Docker 및 Kubernetes 활용
|
||||
- **로드 밸런싱**: 트래픽 분산 처리
|
||||
|
||||
### 성능 최적화
|
||||
- **데이터베이스 인덱싱**: 쿼리 성능 최적화
|
||||
- **캐싱 전략**: Redis를 활용한 다층 캐싱
|
||||
- **CDN 활용**: 정적 자산 전송 최적화
|
||||
- **코드 분할**: 필요한 코드만 로드하는 지연 로딩
|
||||
|
||||
### 모니터링 및 분석
|
||||
- **성능 모니터링**: New Relic 또는 Datadog
|
||||
- **오류 추적**: Sentry
|
||||
- **사용자 행동 분석**: Google Analytics
|
||||
- **서버 모니터링**: Prometheus 및 Grafana
|
||||
|
||||
## 배포 아키텍처
|
||||
|
||||
### Supabase On-Premise 배포
|
||||
- **Docker 컨테이너**: 로컬 서버에 Supabase 서비스 배포
|
||||
- **자체 호스팅**: 내부 네트워크에서 안전하게 데이터 관리
|
||||
- **백업 및 복구**: 자동화된 백업 및 복구 프로세스
|
||||
- **확장성**: 필요에 따라 리소스 확장 가능
|
||||
|
||||
### 개발 환경
|
||||
- **로컬 개발**: Docker 컨테이너
|
||||
- **테스트 환경**: CI/CD 파이프라인 통합
|
||||
- **스테이징 환경**: 프로덕션과 동일한 구성
|
||||
|
||||
### 프로덕션 환경
|
||||
- **클라우드 제공자**: AWS 또는 Google Cloud Platform
|
||||
- **컨테이너 오케스트레이션**: Kubernetes
|
||||
- **CI/CD**: GitHub Actions 또는 Jenkins
|
||||
- **인프라 관리**: Terraform 또는 CloudFormation
|
||||
|
||||
## 결론
|
||||
|
||||
'적자 탈출 가계부' 시스템 아키텍처는 확장성, 보안, 성능을 고려하여 설계되었습니다. 클라이언트-서버 모델을 기반으로 하며, 모바일 환경에서의 사용성과 오프라인 기능을 중요시합니다. 또한 AI/ML 기능을 통합하여 사용자에게 맞춤형 재정 관리 경험을 제공하는 것을 목표로 합니다.
|
||||
|
||||
이 아키텍처는 초기 MVP 단계에서 시작하여 사용자 피드백과 요구사항에 따라 점진적으로 확장될 수 있도록 설계되었습니다. 모듈식 접근 방식을 통해 새로운 기능을 쉽게 추가하고 기존 기능을 개선할 수 있습니다.
|
||||
527
docs/02_기술_문서/02_데이터_모델_설계.md
Normal file
527
docs/02_기술_문서/02_데이터_모델_설계.md
Normal file
@@ -0,0 +1,527 @@
|
||||
# 적자 탈출 가계부 데이터 모델 설계
|
||||
|
||||
## 개요
|
||||
|
||||
'적자 탈출 가계부' 애플리케이션의 데이터 모델은 사용자의 재정 데이터를 효율적으로 저장, 관리, 분석할 수 있도록 설계되었습니다. 본 문서는 데이터베이스 스키마, 주요 엔티티 간의 관계, 그리고 데이터 접근 패턴에 대해 설명합니다.
|
||||
|
||||
## 데이터베이스 선택
|
||||
|
||||
### 주요 데이터베이스: MongoDB
|
||||
|
||||
MongoDB를 주요 데이터베이스로 선택한 이유:
|
||||
- **유연한 스키마**: 사용자별 맞춤형 카테고리, 태그 등 다양한 확장 가능성 지원
|
||||
- **문서 지향적 구조**: 계층적 데이터(예: 거래 내역과 관련 메타데이터) 저장에 적합
|
||||
- **확장성**: 사용자 증가에 따른 수평적 확장 용이
|
||||
- **쿼리 성능**: 복잡한 집계 및 분석 쿼리 지원
|
||||
|
||||
### 보조 데이터베이스: Redis
|
||||
|
||||
Redis를 보조 데이터베이스로 활용:
|
||||
- **캐싱**: 자주 접근하는 데이터(예: 사용자 대시보드 요약) 캐싱
|
||||
- **세션 관리**: 사용자 세션 및 인증 토큰 관리
|
||||
- **실시간 데이터**: 알림 및 실시간 업데이트 관리
|
||||
- **작업 큐**: 비동기 작업(예: 데이터 분석, 보고서 생성) 관리
|
||||
|
||||
## 주요 데이터 엔티티
|
||||
|
||||
### 1. 사용자 (Users)
|
||||
|
||||
사용자 계정 및 프로필 정보를 저장합니다.
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: ObjectId, // 사용자 고유 ID
|
||||
email: String, // 이메일 (로그인 ID)
|
||||
password: String, // 암호화된 비밀번호
|
||||
name: String, // 사용자 이름
|
||||
profileImage: String, // 프로필 이미지 URL
|
||||
phoneNumber: String, // 전화번호 (선택사항)
|
||||
dateOfBirth: Date, // 생년월일 (선택사항)
|
||||
gender: String, // 성별 (선택사항)
|
||||
occupation: String, // 직업 (선택사항)
|
||||
monthlyIncome: Number, // 월 평균 수입
|
||||
financialGoals: [ // 재정 목표 목록
|
||||
{
|
||||
_id: ObjectId, // 목표 ID
|
||||
name: String, // 목표 이름
|
||||
targetAmount: Number, // 목표 금액
|
||||
currentAmount: Number, // 현재 금액
|
||||
deadline: Date, // 목표 기한
|
||||
category: String, // 목표 카테고리
|
||||
isCompleted: Boolean, // 완료 여부
|
||||
createdAt: Date // 생성 일시
|
||||
}
|
||||
],
|
||||
preferences: { // 사용자 설정
|
||||
currency: String, // 통화 단위
|
||||
language: String, // 언어 설정
|
||||
theme: String, // UI 테마
|
||||
notificationSettings: { // 알림 설정
|
||||
budgetAlerts: Boolean, // 예산 알림
|
||||
transactionReminders: Boolean, // 거래 리마인더
|
||||
weeklyReports: Boolean, // 주간 보고서
|
||||
savingsTips: Boolean // 절약 팁
|
||||
}
|
||||
},
|
||||
subscriptionTier: String, // 구독 등급 (free, premium)
|
||||
subscriptionExpiresAt: Date, // 구독 만료일
|
||||
createdAt: Date, // 계정 생성일
|
||||
updatedAt: Date, // 최종 업데이트일
|
||||
lastLoginAt: Date, // 최근 로그인일
|
||||
isActive: Boolean, // 계정 활성화 상태
|
||||
deactivatedAt: Date, // 계정 비활성화일
|
||||
refreshToken: String // 리프레시 토큰
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 거래 내역 (Transactions)
|
||||
|
||||
사용자의 모든 수입, 지출, 이체 거래를 저장합니다.
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: ObjectId, // 거래 고유 ID
|
||||
userId: ObjectId, // 사용자 ID (참조)
|
||||
type: String, // 거래 유형 (income, expense, transfer)
|
||||
amount: Number, // 거래 금액
|
||||
currency: String, // 통화 단위
|
||||
categoryId: ObjectId, // 카테고리 ID (참조)
|
||||
subcategoryId: ObjectId, // 서브카테고리 ID (참조, 선택사항)
|
||||
paymentMethodId: ObjectId, // 결제 수단 ID (참조)
|
||||
accountId: ObjectId, // 계좌 ID (참조)
|
||||
description: String, // 거래 설명
|
||||
memo: String, // 추가 메모
|
||||
transactionDate: Date, // 거래 일시
|
||||
location: { // 거래 위치 (선택사항)
|
||||
name: String, // 장소 이름
|
||||
latitude: Number, // 위도
|
||||
longitude: Number // 경도
|
||||
},
|
||||
tags: [String], // 태그 목록
|
||||
attachments: [ // 첨부 파일 (영수증 등)
|
||||
{
|
||||
url: String, // 파일 URL
|
||||
type: String, // 파일 유형
|
||||
name: String, // 파일 이름
|
||||
uploadedAt: Date // 업로드 일시
|
||||
}
|
||||
],
|
||||
isRecurring: Boolean, // 정기 거래 여부
|
||||
recurringDetails: { // 정기 거래 상세 (선택사항)
|
||||
frequency: String, // 빈도 (daily, weekly, monthly, yearly)
|
||||
interval: Number, // 간격
|
||||
startDate: Date, // 시작일
|
||||
endDate: Date, // 종료일 (선택사항)
|
||||
lastProcessedDate: Date // 마지막 처리일
|
||||
},
|
||||
status: String, // 상태 (pending, completed, cancelled)
|
||||
createdAt: Date, // 생성일
|
||||
updatedAt: Date, // 수정일
|
||||
deletedAt: Date, // 삭제일 (소프트 삭제)
|
||||
isDeleted: Boolean // 삭제 여부
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 카테고리 (Categories)
|
||||
|
||||
거래를 분류하기 위한 카테고리 정보를 저장합니다.
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: ObjectId, // 카테고리 고유 ID
|
||||
userId: ObjectId, // 사용자 ID (참조, null이면 기본 카테고리)
|
||||
name: String, // 카테고리 이름
|
||||
type: String, // 카테고리 유형 (income, expense)
|
||||
icon: String, // 아이콘 식별자
|
||||
color: String, // 색상 코드
|
||||
isDefault: Boolean, // 기본 카테고리 여부
|
||||
isActive: Boolean, // 활성화 상태
|
||||
parentId: ObjectId, // 부모 카테고리 ID (참조, 선택사항)
|
||||
order: Number, // 표시 순서
|
||||
createdAt: Date, // 생성일
|
||||
updatedAt: Date // 수정일
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 예산 (Budgets)
|
||||
|
||||
사용자의 예산 설정 정보를 저장합니다.
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: ObjectId, // 예산 고유 ID
|
||||
userId: ObjectId, // 사용자 ID (참조)
|
||||
name: String, // 예산 이름
|
||||
amount: Number, // 예산 금액
|
||||
currency: String, // 통화 단위
|
||||
period: String, // 기간 유형 (daily, weekly, monthly, yearly)
|
||||
startDate: Date, // 시작일
|
||||
endDate: Date, // 종료일
|
||||
categoryIds: [ObjectId], // 카테고리 ID 목록 (참조)
|
||||
isRecurring: Boolean, // 반복 여부
|
||||
notificationThreshold: Number, // 알림 임계값 (%)
|
||||
createdAt: Date, // 생성일
|
||||
updatedAt: Date, // 수정일
|
||||
isActive: Boolean // 활성화 상태
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 계좌 (Accounts)
|
||||
|
||||
사용자의 금융 계좌 정보를 저장합니다.
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: ObjectId, // 계좌 고유 ID
|
||||
userId: ObjectId, // 사용자 ID (참조)
|
||||
name: String, // 계좌 이름
|
||||
type: String, // 계좌 유형 (checking, savings, credit, investment)
|
||||
institutionName: String, // 금융 기관 이름
|
||||
accountNumber: String, // 계좌 번호 (마스킹 처리)
|
||||
balance: Number, // 현재 잔액
|
||||
currency: String, // 통화 단위
|
||||
isLinked: Boolean, // 외부 API 연동 여부
|
||||
externalId: String, // 외부 API 식별자 (선택사항)
|
||||
lastSyncAt: Date, // 마지막 동기화 일시
|
||||
icon: String, // 아이콘 식별자
|
||||
color: String, // 색상 코드
|
||||
isDefault: Boolean, // 기본 계좌 여부
|
||||
isActive: Boolean, // 활성화 상태
|
||||
createdAt: Date, // 생성일
|
||||
updatedAt: Date // 수정일
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 결제 수단 (PaymentMethods)
|
||||
|
||||
사용자의 결제 수단 정보를 저장합니다.
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: ObjectId, // 결제 수단 고유 ID
|
||||
userId: ObjectId, // 사용자 ID (참조)
|
||||
name: String, // 결제 수단 이름
|
||||
type: String, // 유형 (cash, credit_card, debit_card, bank_transfer, mobile_payment)
|
||||
accountId: ObjectId, // 연결된 계좌 ID (참조, 선택사항)
|
||||
cardInfo: { // 카드 정보 (선택사항)
|
||||
lastFourDigits: String, // 카드 번호 마지막 4자리
|
||||
expiryDate: String, // 만료일
|
||||
cardNetwork: String // 카드 네트워크 (Visa, Mastercard 등)
|
||||
},
|
||||
icon: String, // 아이콘 식별자
|
||||
color: String, // 색상 코드
|
||||
isDefault: Boolean, // 기본 결제 수단 여부
|
||||
isActive: Boolean, // 활성화 상태
|
||||
createdAt: Date, // 생성일
|
||||
updatedAt: Date // 수정일
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 절약 챌린지 (SavingChallenges)
|
||||
|
||||
사용자의 절약 챌린지 정보를 저장합니다.
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: ObjectId, // 챌린지 고유 ID
|
||||
title: String, // 챌린지 제목
|
||||
description: String, // 챌린지 설명
|
||||
type: String, // 챌린지 유형 (system, custom)
|
||||
creatorId: ObjectId, // 생성자 ID (참조, system이면 null)
|
||||
targetAmount: Number, // 목표 절약 금액
|
||||
duration: Number, // 기간 (일 단위)
|
||||
difficulty: String, // 난이도 (easy, medium, hard)
|
||||
categoryIds: [ObjectId], // 관련 카테고리 ID 목록 (참조)
|
||||
tips: [String], // 절약 팁 목록
|
||||
imageUrl: String, // 챌린지 이미지 URL
|
||||
isPublic: Boolean, // 공개 여부
|
||||
participantCount: Number, // 참여자 수
|
||||
successRate: Number, // 성공률 (%)
|
||||
createdAt: Date, // 생성일
|
||||
updatedAt: Date, // 수정일
|
||||
isActive: Boolean // 활성화 상태
|
||||
}
|
||||
```
|
||||
|
||||
### 8. 사용자 챌린지 참여 (UserChallenges)
|
||||
|
||||
사용자의 챌린지 참여 정보를 저장합니다.
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: ObjectId, // 참여 고유 ID
|
||||
userId: ObjectId, // 사용자 ID (참조)
|
||||
challengeId: ObjectId, // 챌린지 ID (참조)
|
||||
startDate: Date, // 시작일
|
||||
endDate: Date, // 종료일
|
||||
targetAmount: Number, // 개인 목표 금액
|
||||
currentAmount: Number, // 현재 절약 금액
|
||||
status: String, // 상태 (in_progress, completed, failed)
|
||||
progress: Number, // 진행률 (%)
|
||||
dailyLogs: [ // 일일 기록
|
||||
{
|
||||
date: Date, // 날짜
|
||||
amount: Number, // 절약 금액
|
||||
note: String // 메모
|
||||
}
|
||||
],
|
||||
completedAt: Date, // 완료일 (선택사항)
|
||||
reward: { // 보상 정보 (선택사항)
|
||||
type: String, // 보상 유형
|
||||
description: String, // 보상 설명
|
||||
claimedAt: Date // 수령일
|
||||
},
|
||||
createdAt: Date, // 생성일
|
||||
updatedAt: Date // 수정일
|
||||
}
|
||||
```
|
||||
|
||||
### 9. 재정 분석 (FinancialAnalytics)
|
||||
|
||||
사용자의 재정 분석 데이터를 저장합니다.
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: ObjectId, // 분석 고유 ID
|
||||
userId: ObjectId, // 사용자 ID (참조)
|
||||
period: String, // 기간 유형 (daily, weekly, monthly, yearly)
|
||||
startDate: Date, // 시작일
|
||||
endDate: Date, // 종료일
|
||||
totalIncome: Number, // 총 수입
|
||||
totalExpense: Number, // 총 지출
|
||||
netCashflow: Number, // 순 현금 흐름
|
||||
savingsRate: Number, // 저축률 (%)
|
||||
categoryBreakdown: [ // 카테고리별 지출 분석
|
||||
{
|
||||
categoryId: ObjectId, // 카테고리 ID (참조)
|
||||
amount: Number, // 금액
|
||||
percentage: Number, // 비율 (%)
|
||||
trend: Number // 전기 대비 변화율 (%)
|
||||
}
|
||||
],
|
||||
topExpenses: [ // 주요 지출 항목
|
||||
{
|
||||
transactionId: ObjectId, // 거래 ID (참조)
|
||||
amount: Number, // 금액
|
||||
description: String, // 설명
|
||||
date: Date // 날짜
|
||||
}
|
||||
],
|
||||
trends: { // 추세 분석
|
||||
incomeGrowth: Number, // 수입 증가율 (%)
|
||||
expenseGrowth: Number, // 지출 증가율 (%)
|
||||
savingsGrowth: Number // 저축 증가율 (%)
|
||||
},
|
||||
financialHealthScore: { // 재정 건강 점수
|
||||
overall: Number, // 종합 점수
|
||||
budgetAdherence: Number, // 예산 준수 점수
|
||||
savingsRate: Number, // 저축률 점수
|
||||
debtManagement: Number, // 부채 관리 점수
|
||||
expenseEfficiency: Number // 지출 효율성 점수
|
||||
},
|
||||
insights: [ // 인사이트 목록
|
||||
{
|
||||
type: String, // 인사이트 유형
|
||||
description: String, // 설명
|
||||
potentialSavings: Number, // 잠재적 절약 금액
|
||||
priority: String // 우선순위 (high, medium, low)
|
||||
}
|
||||
],
|
||||
createdAt: Date, // 생성일
|
||||
updatedAt: Date // 수정일
|
||||
}
|
||||
```
|
||||
|
||||
### 10. 알림 (Notifications)
|
||||
|
||||
사용자에게 전송되는 알림 정보를 저장합니다.
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: ObjectId, // 알림 고유 ID
|
||||
userId: ObjectId, // 사용자 ID (참조)
|
||||
type: String, // 알림 유형 (budget_alert, transaction_reminder, saving_tip, etc)
|
||||
title: String, // 알림 제목
|
||||
message: String, // 알림 내용
|
||||
data: { // 관련 데이터
|
||||
entityType: String, // 엔티티 유형 (budget, transaction, challenge, etc)
|
||||
entityId: ObjectId, // 엔티티 ID (참조)
|
||||
action: String // 필요한 액션
|
||||
},
|
||||
isRead: Boolean, // 읽음 여부
|
||||
readAt: Date, // 읽은 일시
|
||||
isActionTaken: Boolean, // 액션 수행 여부
|
||||
actionTakenAt: Date, // 액션 수행 일시
|
||||
expiresAt: Date, // 만료일 (선택사항)
|
||||
priority: String, // 우선순위 (high, medium, low)
|
||||
createdAt: Date, // 생성일
|
||||
updatedAt: Date // 수정일
|
||||
}
|
||||
```
|
||||
|
||||
## 데이터 관계 다이어그램
|
||||
|
||||
```
|
||||
+-------------+ +----------------+ +-------------+
|
||||
| Users |------>| Transactions |<------| Categories |
|
||||
+-------------+ +----------------+ +-------------+
|
||||
| | |
|
||||
| | |
|
||||
v v v
|
||||
+-------------+ +----------------+ +-------------+
|
||||
| Accounts |------>| PaymentMethods | | Budgets |
|
||||
+-------------+ +----------------+ +-------------+
|
||||
| |
|
||||
| |
|
||||
v v
|
||||
+-------------+ +----------------+ +-------------+
|
||||
| UserChallenges|<---->|SavingChallenges| |FinancialAnalytics|
|
||||
+-------------+ +----------------+ +-------------+
|
||||
|
|
||||
|
|
||||
v
|
||||
+-------------+
|
||||
|Notifications |
|
||||
+-------------+
|
||||
```
|
||||
|
||||
## 인덱싱 전략
|
||||
|
||||
효율적인 쿼리 성능을 위한 인덱스 설계:
|
||||
|
||||
### 사용자 컬렉션
|
||||
- `email`: 로그인 및 사용자 조회
|
||||
- `subscriptionTier`, `subscriptionExpiresAt`: 구독 관리
|
||||
|
||||
### 거래 내역 컬렉션
|
||||
- `userId`: 사용자별 거래 조회
|
||||
- `userId` + `transactionDate`: 날짜별 거래 조회
|
||||
- `userId` + `categoryId`: 카테고리별 거래 조회
|
||||
- `userId` + `type`: 수입/지출별 조회
|
||||
- `userId` + `isRecurring`: 정기 거래 조회
|
||||
|
||||
### 카테고리 컬렉션
|
||||
- `userId`: 사용자별 카테고리 조회
|
||||
- `userId` + `type`: 수입/지출 카테고리 조회
|
||||
|
||||
### 예산 컬렉션
|
||||
- `userId`: 사용자별 예산 조회
|
||||
- `userId` + `period` + `startDate`: 기간별 예산 조회
|
||||
|
||||
### 계좌 컬렉션
|
||||
- `userId`: 사용자별 계좌 조회
|
||||
- `userId` + `type`: 계좌 유형별 조회
|
||||
|
||||
### 절약 챌린지 컬렉션
|
||||
- `isPublic`: 공개 챌린지 조회
|
||||
- `difficulty`: 난이도별 챌린지 조회
|
||||
|
||||
### 사용자 챌린지 참여 컬렉션
|
||||
- `userId`: 사용자별 챌린지 참여 조회
|
||||
- `userId` + `status`: 상태별 챌린지 조회
|
||||
- `challengeId`: 챌린지별 참여자 조회
|
||||
|
||||
### 재정 분석 컬렉션
|
||||
- `userId`: 사용자별 분석 조회
|
||||
- `userId` + `period` + `startDate`: 기간별 분석 조회
|
||||
|
||||
### 알림 컬렉션
|
||||
- `userId`: 사용자별 알림 조회
|
||||
- `userId` + `isRead`: 읽지 않은 알림 조회
|
||||
- `userId` + `priority`: 우선순위별 알림 조회
|
||||
|
||||
## 데이터 접근 패턴
|
||||
|
||||
주요 사용 사례에 따른 데이터 접근 패턴:
|
||||
|
||||
### 1. 대시보드 로딩
|
||||
- 사용자 기본 정보 조회
|
||||
- 최근 거래 내역 조회 (제한된 수)
|
||||
- 현재 월 예산 대비 지출 현황 조회
|
||||
- 재정 건강 점수 조회
|
||||
- 읽지 않은 알림 조회
|
||||
|
||||
### 2. 거래 내역 조회
|
||||
- 기간별 거래 내역 페이지네이션 조회
|
||||
- 카테고리별 필터링
|
||||
- 금액별 정렬
|
||||
|
||||
### 3. 예산 관리
|
||||
- 현재 활성 예산 조회
|
||||
- 카테고리별 예산 대비 지출 현황 조회
|
||||
- 예산 초과 카테고리 식별
|
||||
|
||||
### 4. 재정 분석
|
||||
- 기간별 수입/지출 추이 조회
|
||||
- 카테고리별 지출 분석 조회
|
||||
- 전월 대비 변화율 계산
|
||||
- 절약 인사이트 생성
|
||||
|
||||
### 5. 절약 챌린지
|
||||
- 추천 챌린지 조회
|
||||
- 사용자 참여 챌린지 상태 조회
|
||||
- 챌린지 진행 상황 업데이트
|
||||
|
||||
## 데이터 마이그레이션 전략
|
||||
|
||||
애플리케이션 발전에 따른 데이터 마이그레이션 전략:
|
||||
|
||||
### 1. 스키마 변경 관리
|
||||
- 하위 호환성 유지: 필드 추가 시 기본값 설정
|
||||
- 버전 관리: 스키마 버전 필드 추가
|
||||
- 점진적 마이그레이션: 대규모 데이터 세트의 경우 배치 처리
|
||||
|
||||
### 2. 데이터 백업
|
||||
- 정기적인 전체 백업: 일일 단위
|
||||
- 증분 백업: 시간 단위
|
||||
- 지리적 복제: 재해 복구 대비
|
||||
|
||||
### 3. 데이터 보존 정책
|
||||
- 트랜잭션 데이터: 7년 보존
|
||||
- 분석 데이터: 집계 데이터는 영구 보존, 원시 데이터는 2년 보존
|
||||
- 사용자 활동 로그: 1년 보존
|
||||
- 삭제된 계정 데이터: 30일 후 완전 삭제
|
||||
|
||||
## 데이터 보안
|
||||
|
||||
사용자 재정 데이터 보호를 위한 보안 조치:
|
||||
|
||||
### 1. 데이터 암호화
|
||||
- 저장 데이터(Data at Rest): AES-256 암호화
|
||||
- 전송 중 데이터(Data in Transit): TLS 1.3
|
||||
- 민감 정보(계좌 번호, 카드 정보): 필드 수준 암호화
|
||||
|
||||
### 2. 접근 제어
|
||||
- 역할 기반 접근 제어(RBAC) 구현
|
||||
- 최소 권한 원칙 적용
|
||||
- API 키 및 토큰 관리
|
||||
|
||||
### 3. 감사 및 모니터링
|
||||
- 데이터 접근 로깅
|
||||
- 이상 행동 감지
|
||||
- 정기적인 보안 감사
|
||||
|
||||
## 성능 최적화
|
||||
|
||||
대규모 데이터 처리를 위한 성능 최적화 전략:
|
||||
|
||||
### 1. 쿼리 최적화
|
||||
- 적절한 인덱스 사용
|
||||
- 프로젝션을 통한 필요 필드만 조회
|
||||
- 페이지네이션 구현
|
||||
|
||||
### 2. 캐싱 전략
|
||||
- 자주 접근하는 데이터 Redis 캐싱
|
||||
- 사용자별 대시보드 데이터 캐싱
|
||||
- 분석 결과 캐싱
|
||||
|
||||
### 3. 데이터 집계
|
||||
- 실시간 집계 대신 사전 계산된 집계 데이터 활용
|
||||
- 배치 처리를 통한 분석 데이터 생성
|
||||
- 시계열 데이터 최적화
|
||||
|
||||
## 결론
|
||||
|
||||
'적자 탈출 가계부' 애플리케이션의 데이터 모델은 사용자의 재정 데이터를 효율적으로 관리하고 분석하기 위해 설계되었습니다. MongoDB의 유연한 스키마를 활용하여 다양한 사용자 요구사항을 수용하고, Redis를 통한 캐싱으로 성능을 최적화하였습니다.
|
||||
|
||||
이 데이터 모델은 초기 MVP 출시 후 사용자 피드백과 실제 사용 패턴에 따라 지속적으로 개선될 예정입니다. 특히 AI 기반 분석 및 추천 기능 강화를 위한 데이터 구조 최적화가 향후 중점적으로 이루어질 계획입니다.
|
||||
135
docs/02_기술_문서/ERD_다이어그램.md
Normal file
135
docs/02_기술_문서/ERD_다이어그램.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# 적자 탈출 가계부 - ERD 다이어그램
|
||||
|
||||
## 엔티티 관계 다이어그램 (ERD)
|
||||
|
||||
아래는 적자 탈출 가계부 앱의 데이터베이스 구조를 표현한 ERD입니다. 이 다이어그램은 각 테이블 간의 관계와 주요 필드를 보여줍니다.
|
||||
|
||||
```
|
||||
+----------------+ +----------------+ +----------------+
|
||||
| Users | | Cards | | Categories |
|
||||
+----------------+ +----------------+ +----------------+
|
||||
| PK id |<----->| PK id | | PK id |
|
||||
| email | | FK user_id | | name |
|
||||
| password_hash | | name | | icon |
|
||||
| name | | card_type | | color |
|
||||
| profile_image | | card_number_4 | | FK parent_id |
|
||||
| created_at | | issuer | | is_system |
|
||||
| updated_at | | color | | is_active |
|
||||
| last_login_at | | payment_day | | created_at |
|
||||
| notif_settings | | monthly_limit | | updated_at |
|
||||
| theme_pref | | is_active | +----------------+
|
||||
| is_active | | created_at | ^
|
||||
+----------------+ | updated_at | |
|
||||
^ +----------------+ |
|
||||
| ^ |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
+----------------+ +----------------+ +----------------+
|
||||
| Notifications | | Expenses | | Templates |
|
||||
+----------------+ +----------------+ +----------------+
|
||||
| PK id | | PK id | | PK id |
|
||||
| FK user_id | | FK user_id | | FK user_id |
|
||||
| title | | FK card_id | | name |
|
||||
| message | | FK category_id | | FK category_id |
|
||||
| type | | amount | | amount |
|
||||
| entity_type | | merchant | | merchant |
|
||||
| entity_id | | transaction_dt | | description |
|
||||
| is_read | | description | | is_favorite |
|
||||
| created_at | | location | | created_at |
|
||||
| read_at | | is_recurring | | updated_at |
|
||||
+----------------+ | receipt_image | +----------------+
|
||||
| tags |
|
||||
| created_at |
|
||||
| updated_at |
|
||||
+----------------+
|
||||
^
|
||||
|
|
||||
|
|
||||
+----------------+
|
||||
| Limits |
|
||||
+----------------+
|
||||
| PK id |
|
||||
| FK user_id |
|
||||
| FK card_id |
|
||||
| FK category_id |
|
||||
| amount |
|
||||
| period |
|
||||
| start_date |
|
||||
| end_date |
|
||||
| is_active |
|
||||
| created_at |
|
||||
| updated_at |
|
||||
+----------------+
|
||||
```
|
||||
|
||||
## 관계 설명
|
||||
|
||||
1. **Users (사용자)**
|
||||
- 사용자는 여러 개의 카드를 가질 수 있음 (1:N)
|
||||
- 사용자는 여러 개의 지출 내역을 가질 수 있음 (1:N)
|
||||
- 사용자는 여러 개의 한도를 설정할 수 있음 (1:N)
|
||||
- 사용자는 여러 개의 알림을 받을 수 있음 (1:N)
|
||||
- 사용자는 여러 개의 템플릿을 생성할 수 있음 (1:N)
|
||||
|
||||
2. **Cards (카드)**
|
||||
- 카드는 한 명의 사용자에게 속함 (N:1)
|
||||
- 카드는 여러 개의 지출 내역과 연결됨 (1:N)
|
||||
- 카드는 여러 개의 한도 설정과 연결될 수 있음 (1:N)
|
||||
|
||||
3. **Categories (카테고리)**
|
||||
- 카테고리는 여러 개의 지출 내역과 연결됨 (1:N)
|
||||
- 카테고리는 여러 개의 한도 설정과 연결될 수 있음 (1:N)
|
||||
- 카테고리는 여러 개의 템플릿과 연결될 수 있음 (1:N)
|
||||
- 카테고리는 자기 참조 관계를 가짐 (부모-자식 계층 구조)
|
||||
|
||||
4. **Expenses (지출)**
|
||||
- 지출은 한 명의 사용자에게 속함 (N:1)
|
||||
- 지출은 하나의 카드와 연결됨 (N:1)
|
||||
- 지출은 하나의 카테고리와 연결됨 (N:1)
|
||||
|
||||
5. **Limits (한도)**
|
||||
- 한도는 한 명의 사용자에게 속함 (N:1)
|
||||
- 한도는 선택적으로 하나의 카드와 연결될 수 있음 (N:1)
|
||||
- 한도는 선택적으로 하나의 카테고리와 연결될 수 있음 (N:1)
|
||||
|
||||
6. **Notifications (알림)**
|
||||
- 알림은 한 명의 사용자에게 속함 (N:1)
|
||||
- 알림은 선택적으로 다른 엔티티(카드, 지출 등)와 연결될 수 있음
|
||||
|
||||
7. **Templates (템플릿)**
|
||||
- 템플릿은 한 명의 사용자에게 속함 (N:1)
|
||||
- 템플릿은 하나의 카테고리와 연결됨 (N:1)
|
||||
|
||||
## 주요 제약 조건
|
||||
|
||||
1. **외래 키 제약 조건**
|
||||
- 모든 외래 키는 참조 무결성을 유지해야 함
|
||||
- 사용자가 삭제되면 해당 사용자의 모든 관련 데이터도 삭제됨 (CASCADE)
|
||||
- 카테고리가 삭제되면 관련 지출은 '기타' 카테고리로 이동 (SET DEFAULT)
|
||||
|
||||
2. **유일성 제약 조건**
|
||||
- 사용자 이메일은 유일해야 함
|
||||
- 카테고리 이름은 같은 부모 내에서 유일해야 함
|
||||
|
||||
3. **필수 필드**
|
||||
- 지출에는 반드시 금액, 날짜, 카테고리가 있어야 함
|
||||
- 한도에는 반드시 금액, 기간, 시작일이 있어야 함
|
||||
|
||||
## 인덱스 전략
|
||||
|
||||
성능 최적화를 위해 다음과 같은 인덱스가 생성됩니다:
|
||||
|
||||
1. **검색 최적화**
|
||||
- expenses(user_id, transaction_date): 날짜별 지출 검색 최적화
|
||||
- expenses(user_id, category_id): 카테고리별 지출 검색 최적화
|
||||
- expenses(user_id, card_id): 카드별 지출 검색 최적화
|
||||
|
||||
2. **정렬 최적화**
|
||||
- expenses(amount): 금액별 정렬 최적화
|
||||
- notifications(created_at): 알림 시간순 정렬 최적화
|
||||
|
||||
3. **필터링 최적화**
|
||||
- cards(is_active): 활성 카드 필터링
|
||||
- limits(is_active): 활성 한도 필터링
|
||||
- notifications(is_read): 읽지 않은 알림 필터링
|
||||
396
docs/02_기술_문서/Lovable_UI_컴포넌트_정리.md
Normal file
396
docs/02_기술_문서/Lovable_UI_컴포넌트_정리.md
Normal file
@@ -0,0 +1,396 @@
|
||||
# Lovable UI 컴포넌트 정리
|
||||
|
||||
## 개요
|
||||
|
||||
Zellyy Finance 앱은 Flutter에서 React와 Tailwind CSS를 사용한 Capacitor 기반 웹 앱으로 전환되었으며, UI 디자인은 뉴모피즘 스타일의 Lovable UI 컴포넌트를 적용하여 차별화된 사용자 경험을 제공하고자 합니다. 이 문서는 Lovable UI 컴포넌트와 관련된 내용을 정리합니다.
|
||||
|
||||
## Lovable UI 컴포넌트 관련 문서
|
||||
|
||||
### 1. UI/UX 설계 문서에서의 Lovable UI
|
||||
|
||||
**위치**: `/01_기획_및_설계/02_UI_UX_설계.md`
|
||||
|
||||
**주요 내용**:
|
||||
- **컴포넌트 라이브러리**: Lovable UI 컴포넌트 라이브러리 구축
|
||||
- **뉴모피즘 스타일**: 입체적이고 부드러운 UI 디자인 적용
|
||||
- **디자인 철학**: 웹 기반 기술과 Capacitor를 활용하여 다양한 플랫폼에서 일관된 사용자 경험을 제공하면서도, 뉴모피즘 스타일의 Lovable UI 컴포넌트를 통해 차별화된 디자인을 구현
|
||||
|
||||
### 2. 시스템 아키텍처 문서에서의 Lovable UI
|
||||
|
||||
**위치**: `/02_기술_문서/01_시스템_아키텍처.md`
|
||||
|
||||
**주요 내용**:
|
||||
- **프레젠테이션 계층**: 뉴모피즘 스타일 UI 컴포넌트 적용
|
||||
|
||||
### 3. 개발 로드맵 문서에서의 Lovable UI
|
||||
|
||||
**위치**: `/03_개발_단계/01_개발_로드맵.md`
|
||||
|
||||
**주요 내용**:
|
||||
- **개발 작업**: 뉴모피즘 스타일 UI 컴포넌트 개발 계획
|
||||
|
||||
### 4. README 문서에서의 Lovable UI
|
||||
|
||||
**위치**: `/README.md`
|
||||
|
||||
**주요 내용**:
|
||||
- **변경 사항**: 2025-03-09: 개발 방법 변경 - Flutter에서 React, Tailwind CSS, Capacitor 기반 웹 앱으로 전환, Lovable UI 컴포넌트 스타일 적용
|
||||
|
||||
## 필요한 Lovable UI 컴포넌트 목록
|
||||
|
||||
Zellyy Finance 앱의 홈 화면을 Lovable UI 컴포넌트로 변경하는 작업을 진행 중입니다. 기존 홈 화면의 기능을 유지하면서 Lovable UI 디자인 시스템의 컴포넌트로 UI를 개선하려고 합니다.
|
||||
|
||||
### 주요 변경 사항
|
||||
1. FloatingActionButton을 LovableAddTransactionButton으로 교체
|
||||
2. 기존 Card를 LovableCard로 교체
|
||||
3. 거래 내역 리스트 아이템을 LovableTransactionCard로 교체
|
||||
4. 전체적인 UI 디자인을 뉴모피즘 스타일로 변경
|
||||
|
||||
### 필요한 컴포넌트
|
||||
- **LovableButton**: 기본 버튼 컴포넌트
|
||||
- **LovableCard**: 카드 컴포넌트
|
||||
- **LovableTransactionCard**: 거래 내역 표시용 카드 컴포넌트
|
||||
- **LovableAddTransactionButton**: 거래 추가용 플로팅 액션 버튼
|
||||
|
||||
### 홈 화면 파일 경로
|
||||
`/Users/hansoo./Dev/Zellyy_Finance/neumofinance/src/pages/Home.tsx`
|
||||
|
||||
## 뉴모피즘 디자인 특징
|
||||
|
||||
뉴모피즘(Neumorphism)은 다음과 같은 특징을 가진 UI 디자인 스타일입니다:
|
||||
|
||||
1. **입체감**: 요소가 배경에서 살짝 튀어나온 것처럼 보이는 효과
|
||||
2. **부드러운 그림자**: 요소의 위쪽과 왼쪽에는 밝은 그림자, 아래쪽과 오른쪽에는 어두운 그림자를 적용
|
||||
3. **단일 색상 사용**: 배경과 요소가 비슷한 색상을 사용하여 미묘한 차이로 구분
|
||||
4. **미니멀리즘**: 깔끔하고 단순한 디자인 요소 사용
|
||||
5. **낮은 대비**: 강한 색상 대비보다는 미묘한 그림자와 하이라이트로 구분
|
||||
|
||||
## 구현 방향
|
||||
|
||||
1. **Tailwind CSS 활용**: 웹 기반 앱에서는 Tailwind CSS를 활용하여 뉴모피즘 효과 구현
|
||||
2. **React 컴포넌트 모듈화**: 재사용 가능한 React 컴포넌트로 설계
|
||||
3. **반응형 디자인**: 다양한 화면 크기에 대응하는 디자인 적용
|
||||
4. **접근성 고려**: 시각적 효과가 접근성을 해치지 않도록 주의
|
||||
5. **성능 최적화**: 그림자 효과 등이 성능에 영향을 미치지 않도록 최적화
|
||||
|
||||
## React 기반 Lovable UI 컴포넌트 구현
|
||||
|
||||
### 1. LovableButton 컴포넌트
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
interface LovableButtonProps {
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
variant?: 'primary' | 'secondary' | 'outline';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const LovableButton: React.FC<LovableButtonProps> = ({
|
||||
children,
|
||||
onClick,
|
||||
className = '',
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
disabled = false,
|
||||
}) => {
|
||||
const baseStyles = 'rounded-xl font-medium transition-all duration-200 focus:outline-none';
|
||||
|
||||
const variantStyles = {
|
||||
primary: 'bg-primary-100 text-primary-900 shadow-neumo hover:shadow-neumo-pressed active:shadow-neumo-pressed',
|
||||
secondary: 'bg-secondary-100 text-secondary-900 shadow-neumo hover:shadow-neumo-pressed active:shadow-neumo-pressed',
|
||||
outline: 'bg-transparent border-2 border-primary-200 text-primary-900 hover:bg-primary-50 active:bg-primary-100',
|
||||
};
|
||||
|
||||
const sizeStyles = {
|
||||
sm: 'px-3 py-1 text-sm',
|
||||
md: 'px-4 py-2',
|
||||
lg: 'px-6 py-3 text-lg',
|
||||
};
|
||||
|
||||
const disabledStyles = disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer';
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={twMerge(
|
||||
baseStyles,
|
||||
variantStyles[variant],
|
||||
sizeStyles[size],
|
||||
disabledStyles,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default LovableButton;
|
||||
```
|
||||
|
||||
### 2. LovableCard 컴포넌트
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
interface LovableCardProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
elevated?: boolean;
|
||||
pressed?: boolean;
|
||||
}
|
||||
|
||||
const LovableCard: React.FC<LovableCardProps> = ({
|
||||
children,
|
||||
className = '',
|
||||
onClick,
|
||||
elevated = false,
|
||||
pressed = false,
|
||||
}) => {
|
||||
const baseStyles = 'bg-primary-50 rounded-2xl p-4 transition-all duration-200';
|
||||
|
||||
const shadowStyles = {
|
||||
default: 'shadow-neumo',
|
||||
elevated: 'shadow-neumo-elevated',
|
||||
pressed: 'shadow-neumo-pressed',
|
||||
};
|
||||
|
||||
const selectedShadow = pressed ? shadowStyles.pressed : (elevated ? shadowStyles.elevated : shadowStyles.default);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
baseStyles,
|
||||
selectedShadow,
|
||||
onClick ? 'cursor-pointer' : '',
|
||||
className
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LovableCard;
|
||||
```
|
||||
|
||||
### 3. LovableTransactionCard 컴포넌트
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import LovableCard from './LovableCard';
|
||||
import { formatCurrency } from '../utils/formatters';
|
||||
|
||||
interface TransactionType {
|
||||
id: string;
|
||||
title: string;
|
||||
amount: number;
|
||||
date: string;
|
||||
category: string;
|
||||
type: 'income' | 'expense';
|
||||
}
|
||||
|
||||
interface LovableTransactionCardProps {
|
||||
transaction: TransactionType;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const LovableTransactionCard: React.FC<LovableTransactionCardProps> = ({
|
||||
transaction,
|
||||
className = '',
|
||||
onClick,
|
||||
}) => {
|
||||
const { title, amount, date, category, type } = transaction;
|
||||
|
||||
// 카테고리에 따른 아이콘 선택
|
||||
const getCategoryIcon = (category: string) => {
|
||||
switch (category.toLowerCase()) {
|
||||
case '식비':
|
||||
return '🍴';
|
||||
case '교통':
|
||||
return '🚗';
|
||||
case '쇼핑':
|
||||
return '🛍️';
|
||||
case '여가':
|
||||
return '⛲️';
|
||||
case '금융':
|
||||
return '💰';
|
||||
case '급여':
|
||||
return '💸';
|
||||
default:
|
||||
return '📃';
|
||||
}
|
||||
};
|
||||
|
||||
const formattedDate = new Date(date).toLocaleDateString('ko-KR', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
|
||||
return (
|
||||
<LovableCard
|
||||
className={twMerge('flex items-center justify-between', className)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="w-10 h-10 rounded-full bg-primary-100 flex items-center justify-center mr-3 shadow-neumo-sm">
|
||||
<span className="text-xl">{getCategoryIcon(category)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-primary-900">{title}</h3>
|
||||
<p className="text-sm text-primary-600">{formattedDate}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`font-bold ${type === 'income' ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{type === 'income' ? '+' : '-'}{formatCurrency(amount)}
|
||||
</div>
|
||||
</LovableCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default LovableTransactionCard;
|
||||
```
|
||||
|
||||
### 4. LovableAddTransactionButton 컴포넌트
|
||||
|
||||
```tsx
|
||||
import React, { useState } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
interface LovableAddTransactionButtonProps {
|
||||
onAddIncome?: () => void;
|
||||
onAddExpense?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const LovableAddTransactionButton: React.FC<LovableAddTransactionButtonProps> = ({
|
||||
onAddIncome,
|
||||
onAddExpense,
|
||||
className = '',
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const toggleOpen = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
const handleAddIncome = () => {
|
||||
if (onAddIncome) {
|
||||
onAddIncome();
|
||||
}
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleAddExpense = () => {
|
||||
if (onAddExpense) {
|
||||
onAddExpense();
|
||||
}
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={twMerge('fixed bottom-6 right-6 z-50', className)}>
|
||||
{isOpen && (
|
||||
<div className="flex flex-col items-end mb-4 space-y-3">
|
||||
<button
|
||||
onClick={handleAddIncome}
|
||||
className="flex items-center bg-primary-50 text-green-600 px-4 py-2 rounded-xl shadow-neumo hover:shadow-neumo-pressed transition-all duration-200"
|
||||
>
|
||||
<span className="mr-2">💸</span>
|
||||
<span>수입 추가</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAddExpense}
|
||||
className="flex items-center bg-primary-50 text-red-600 px-4 py-2 rounded-xl shadow-neumo hover:shadow-neumo-pressed transition-all duration-200"
|
||||
>
|
||||
<span className="mr-2">💳</span>
|
||||
<span>지출 추가</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={toggleOpen}
|
||||
className={`w-14 h-14 rounded-full bg-primary-100 text-primary-900 flex items-center justify-center shadow-neumo-elevated hover:shadow-neumo transition-all duration-300 ${isOpen ? 'rotate-45' : ''}`}
|
||||
>
|
||||
<span className="text-2xl font-bold">+</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LovableAddTransactionButton;
|
||||
```
|
||||
|
||||
### 5. Tailwind CSS 구성
|
||||
|
||||
```ts
|
||||
// tailwind.config.ts
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#f5f7fa',
|
||||
100: '#e4e7eb',
|
||||
200: '#cbd2d9',
|
||||
300: '#9aa5b1',
|
||||
400: '#7b8794',
|
||||
500: '#616e7c',
|
||||
600: '#52606d',
|
||||
700: '#3e4c59',
|
||||
800: '#323f4b',
|
||||
900: '#1f2933',
|
||||
},
|
||||
secondary: {
|
||||
50: '#e3f8ff',
|
||||
100: '#b3ecff',
|
||||
200: '#81defd',
|
||||
300: '#5ed0fa',
|
||||
400: '#40c3f7',
|
||||
500: '#2bb0ed',
|
||||
600: '#1992d4',
|
||||
700: '#127fbf',
|
||||
800: '#0b69a3',
|
||||
900: '#035388',
|
||||
},
|
||||
},
|
||||
boxShadow: {
|
||||
'neumo': '5px 5px 10px #d1d9e6, -5px -5px 10px #ffffff',
|
||||
'neumo-sm': '3px 3px 6px #d1d9e6, -3px -3px 6px #ffffff',
|
||||
'neumo-pressed': 'inset 5px 5px 10px #d1d9e6, inset -5px -5px 10px #ffffff',
|
||||
'neumo-elevated': '10px 10px 20px #d1d9e6, -10px -10px 20px #ffffff',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
## 결론
|
||||
|
||||
Lovable UI 컴포넌트는 Zellyy Finance 앱의 차별화된 사용자 경험을 제공하기 위한 핵심 요소입니다. Flutter에서 React와 Tailwind CSS를 사용한 웹 기반 앱으로 전환하면서, 뉴모피즘 스타일을 적용하여 입체적이고 부드러운 디자인을 구현하였습니다.
|
||||
|
||||
React의 컴포넌트 기반 아키텍처와 Tailwind CSS의 유틸리티 클래스를 활용하여 재사용 가능한 컴포넌트를 구축함으로써, 개발 효율성을 높이고 일관된 사용자 경험을 제공할 수 있습니다. 특히 커스텀 그림자 효과를 활용한 뉴모피즘 스타일은 사용자에게 매력적인 인터페이스를 제공합니다.
|
||||
|
||||
이러한 Lovable UI 컴포넌트 라이브러리는 앞으로도 지속적으로 개선되고 확장될 예정이며, 사용자 피드백을 반영하여 더욱 향상된 경험을 제공할 계획입니다. 이를 통해 Zellyy Finance 앱은 사용자에게 차별화된 가치를 제공하고, 재무 관리에 도움이 되는 유용한 도구로 자리매김할 것입니다.
|
||||
408
docs/02_기술_문서/Nginx_Supabase_설치_가이드.md
Normal file
408
docs/02_기술_문서/Nginx_Supabase_설치_가이드.md
Normal file
@@ -0,0 +1,408 @@
|
||||
# Nginx와 Supabase 설치 가이드
|
||||
|
||||
이 문서는 Debian 12 서버에 Nginx와 Supabase를 설치하고 설정하는 과정을 안내합니다.
|
||||
|
||||
## 1. 서버 접속
|
||||
|
||||
SSH 키를 사용하여 서버에 접속합니다:
|
||||
|
||||
```bash
|
||||
ssh a11
|
||||
```
|
||||
|
||||
또는 전체 명령어를 사용할 수도 있습니다:
|
||||
|
||||
```bash
|
||||
ssh -i ~/.ssh/id_ed25519_ism ism-admin@a11.ism.kr
|
||||
```
|
||||
|
||||
## 2. 시스템 업데이트
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt upgrade -y
|
||||
```
|
||||
|
||||
## 3. Nginx 설치 및 설정
|
||||
|
||||
### 3.1 Nginx 설치
|
||||
|
||||
```bash
|
||||
sudo apt install -y nginx
|
||||
```
|
||||
|
||||
### 3.2 방화벽 설정
|
||||
|
||||
```bash
|
||||
sudo apt install -y ufw
|
||||
sudo ufw allow 'Nginx Full' # 80, 443 포트 허용
|
||||
sudo ufw allow 'OpenSSH' # SSH 포트 허용
|
||||
sudo ufw enable
|
||||
```
|
||||
|
||||
### 3.3 SSL 인증서 발급 (Let's Encrypt)
|
||||
|
||||
```bash
|
||||
sudo apt install -y certbot python3-certbot-nginx
|
||||
sudo certbot --nginx -d a11.ism.kr
|
||||
```
|
||||
|
||||
### 3.4 Nginx 설정 파일 생성
|
||||
|
||||
```bash
|
||||
sudo nano /etc/nginx/sites-available/supabase
|
||||
```
|
||||
|
||||
다음 내용을 파일에 추가합니다:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name a11.ism.kr;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name a11.ism.kr;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/a11.ism.kr/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/a11.ism.kr/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
|
||||
add_header X-Frame-Options DENY;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
|
||||
# Kong API
|
||||
location /api {
|
||||
proxy_pass http://localhost:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Kong Admin API
|
||||
location /kong {
|
||||
proxy_pass http://localhost:8001;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Studio
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# GoTrue
|
||||
location /auth {
|
||||
proxy_pass http://localhost:9999;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# PostgREST
|
||||
location /rest/v1 {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Storage
|
||||
location /storage/v1 {
|
||||
proxy_pass http://localhost:5000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# WebSocket 지원
|
||||
location /realtime/v1 {
|
||||
proxy_pass http://localhost:4000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
설정 파일을 활성화하고 Nginx를 재시작합니다:
|
||||
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/supabase /etc/nginx/sites-enabled/
|
||||
sudo nginx -t # 설정 파일 문법 검사
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
## 4. Docker 및 Docker Compose 설치
|
||||
|
||||
### 4.1 Docker 설치
|
||||
|
||||
```bash
|
||||
sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt update
|
||||
sudo apt install -y docker-ce docker-ce-cli containerd.io
|
||||
sudo usermod -aG docker $USER
|
||||
```
|
||||
|
||||
### 4.2 Docker Compose 설치
|
||||
|
||||
```bash
|
||||
sudo apt install -y docker-compose-plugin
|
||||
```
|
||||
|
||||
## 5. Supabase 설치
|
||||
|
||||
### 5.1 Supabase 프로젝트 클론
|
||||
|
||||
```bash
|
||||
mkdir -p ~/supabase
|
||||
cd ~/supabase
|
||||
git clone --depth 1 https://github.com/supabase/supabase
|
||||
cd supabase/docker
|
||||
```
|
||||
|
||||
### 5.2 환경 변수 설정
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
다음과 같이 환경 변수를 설정합니다:
|
||||
|
||||
```
|
||||
# Supabase 설정
|
||||
POSTGRES_PASSWORD=zellyy_finance_secure_password_2025
|
||||
JWT_SECRET=$(openssl rand -base64 32)
|
||||
ANON_KEY=$(openssl rand -base64 32)
|
||||
SERVICE_ROLE_KEY=$(openssl rand -base64 32)
|
||||
|
||||
# API 및 사이트 URL
|
||||
SITE_URL=https://a11.ism.kr
|
||||
API_EXTERNAL_URL=https://a11.ism.kr
|
||||
STUDIO_PORT=3000
|
||||
|
||||
# 대시보드 인증
|
||||
DASHBOARD_USERNAME=hansoo
|
||||
DASHBOARD_PASSWORD=zellyy_secure_dashboard_password_2025
|
||||
|
||||
# 이메일 설정 (선택 사항)
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=
|
||||
SMTP_USER=
|
||||
SMTP_PASS=
|
||||
SMTP_SENDER_NAME=
|
||||
```
|
||||
|
||||
### 5.3 Supabase 실행
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 5.4 상태 확인
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
## 6. 인증 정보 기록
|
||||
|
||||
설치 후 생성된 API 키와 JWT 시크릿을 기록합니다. 이 정보는 `.env` 파일에서 확인할 수 있습니다:
|
||||
|
||||
```bash
|
||||
cd ~/supabase/supabase/docker
|
||||
grep -E "JWT_SECRET|ANON_KEY|SERVICE_ROLE_KEY" .env
|
||||
```
|
||||
|
||||
이 정보를 `/Users/hansoo./Documents/my vault/1. Project/ZELLYY/적자 탈출 가계부/02_기술_문서/Supabase_인증_정보.md` 파일에 기록합니다.
|
||||
|
||||
## 7. 데이터베이스 초기 설정
|
||||
|
||||
### 7.1 PostgreSQL 접속
|
||||
|
||||
```bash
|
||||
docker exec -it supabase-db psql -U postgres
|
||||
```
|
||||
|
||||
### 7.2 기본 스키마 생성
|
||||
|
||||
```sql
|
||||
-- 사용자 스키마 생성
|
||||
CREATE SCHEMA app;
|
||||
|
||||
-- Row Level Security 활성화
|
||||
ALTER TABLE app.users ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- 기본 정책 설정
|
||||
CREATE POLICY "사용자는 자신의 데이터만 볼 수 있음" ON app.users
|
||||
FOR ALL
|
||||
USING (auth.uid() = user_id);
|
||||
```
|
||||
|
||||
## 8. 백업 설정
|
||||
|
||||
### 8.1 자동 백업 스크립트 생성
|
||||
|
||||
```bash
|
||||
mkdir -p ~/backup-scripts
|
||||
nano ~/backup-scripts/backup-postgres.sh
|
||||
```
|
||||
|
||||
다음 내용을 파일에 추가합니다:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
BACKUP_DIR="/home/ism-admin/backups"
|
||||
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||
BACKUP_FILE="$BACKUP_DIR/postgres_backup_$TIMESTAMP.sql.gz"
|
||||
|
||||
# 백업 디렉토리 생성
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# 데이터베이스 백업
|
||||
docker exec supabase-db pg_dumpall -U postgres | gzip > $BACKUP_FILE
|
||||
|
||||
# 30일 이상 된 백업 삭제
|
||||
find $BACKUP_DIR -name "postgres_backup_*.sql.gz" -type f -mtime +30 -delete
|
||||
```
|
||||
|
||||
스크립트에 실행 권한 부여:
|
||||
|
||||
```bash
|
||||
chmod +x ~/backup-scripts/backup-postgres.sh
|
||||
```
|
||||
|
||||
### 8.2 Cron 작업 설정
|
||||
|
||||
```bash
|
||||
crontab -e
|
||||
```
|
||||
|
||||
다음 내용을 추가하여 매일 새벽 3시에 백업을 실행합니다:
|
||||
|
||||
```
|
||||
0 3 * * * /home/ism-admin/backup-scripts/backup-postgres.sh
|
||||
```
|
||||
|
||||
## 9. 모니터링 설정
|
||||
|
||||
### 9.1 기본 모니터링
|
||||
|
||||
```bash
|
||||
sudo apt install -y htop
|
||||
```
|
||||
|
||||
### 9.2 로그 모니터링
|
||||
|
||||
```bash
|
||||
# 모든 컨테이너 로그 확인
|
||||
docker compose logs
|
||||
|
||||
# 특정 서비스 로그 확인 (예: PostgreSQL)
|
||||
docker compose logs db
|
||||
```
|
||||
|
||||
## 10. 문제 해결
|
||||
|
||||
### 10.1 Nginx 문제
|
||||
|
||||
```bash
|
||||
# Nginx 상태 확인
|
||||
sudo systemctl status nginx
|
||||
|
||||
# Nginx 로그 확인
|
||||
sudo tail -f /var/log/nginx/error.log
|
||||
```
|
||||
|
||||
### 10.2 Docker 문제
|
||||
|
||||
```bash
|
||||
# Docker 상태 확인
|
||||
docker ps
|
||||
|
||||
# Docker 로그 확인
|
||||
docker logs supabase-db
|
||||
```
|
||||
|
||||
### 10.3 인증서 갱신
|
||||
|
||||
```bash
|
||||
sudo certbot renew --dry-run
|
||||
```
|
||||
|
||||
## 11. 유지 관리
|
||||
|
||||
### 11.1 Supabase 업데이트
|
||||
|
||||
```bash
|
||||
cd ~/supabase/supabase/docker
|
||||
git pull
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 11.2 시스템 업데이트
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt upgrade -y
|
||||
```
|
||||
|
||||
## 12. 보안 강화
|
||||
|
||||
### 12.1 SSH 보안 강화
|
||||
|
||||
```bash
|
||||
sudo nano /etc/ssh/sshd_config
|
||||
```
|
||||
|
||||
다음 설정을 추가/수정합니다:
|
||||
|
||||
```
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication no
|
||||
X11Forwarding no
|
||||
```
|
||||
|
||||
SSH 서비스 재시작:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart sshd
|
||||
```
|
||||
|
||||
### 12.2 방화벽 확인
|
||||
|
||||
```bash
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
## 13. 완료
|
||||
|
||||
이제 Nginx와 Supabase가 설치되었습니다. 다음 URL로 접속하여 확인할 수 있습니다:
|
||||
|
||||
- Supabase Studio: https://a11.ism.kr
|
||||
- API 엔드포인트: https://a11.ism.kr/api
|
||||
|
||||
설치 후 생성된 API 키와 JWT 시크릿을 `Supabase_인증_정보.md` 파일에 기록하는 것을 잊지 마세요.
|
||||
657
docs/02_기술_문서/Supabase_설정_가이드.md
Normal file
657
docs/02_기술_문서/Supabase_설정_가이드.md
Normal file
@@ -0,0 +1,657 @@
|
||||
# Supabase 설정 가이드 - Debian 12 서버 설치
|
||||
|
||||
## 1. Supabase 소개
|
||||
|
||||
Supabase는 PostgreSQL 데이터베이스를 기반으로 하는 백엔드 서비스입니다. 다음과 같은 기능을 제공합니다:
|
||||
|
||||
- **데이터베이스**: PostgreSQL 데이터베이스
|
||||
- **인증**: 사용자 인증 및 권한 관리
|
||||
- **스토리지**: 파일 저장 및 관리
|
||||
- **API**: 자동 생성되는 RESTful API 및 실시간 구독
|
||||
- **Edge Functions**: 서버리스 함수
|
||||
|
||||
이 가이드에서는 Debian 12 서버에 Supabase를 직접 설치하고 설정하는 방법을 안내합니다.
|
||||
|
||||
## 2. Debian 12 서버에 Supabase 설치
|
||||
|
||||
### 2.1 사전 요구 사항
|
||||
|
||||
1. Debian 12가 설치된 서버
|
||||
2. 루트 또는 sudo 권한이 있는 사용자
|
||||
3. 최소 사양:
|
||||
- CPU: 2코어 이상
|
||||
- RAM: 4GB 이상
|
||||
- 저장공간: 20GB 이상
|
||||
- 고정 IP 주소 (권장)
|
||||
|
||||
### 2.2 Docker 및 Docker Compose 설치
|
||||
|
||||
Supabase는 Docker 컨테이너로 실행되므로 먼저 Docker와 Docker Compose를 설치해야 합니다:
|
||||
|
||||
```bash
|
||||
# 시스템 업데이트
|
||||
sudo apt update
|
||||
sudo apt upgrade -y
|
||||
|
||||
# 필요한 패키지 설치
|
||||
sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
|
||||
|
||||
# Docker 공식 GPG 키 추가
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
|
||||
# Docker 저장소 추가
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
# Docker 설치
|
||||
sudo apt update
|
||||
sudo apt install -y docker-ce docker-ce-cli containerd.io
|
||||
|
||||
# 현재 사용자를 docker 그룹에 추가 (sudo 없이 Docker 사용 가능)
|
||||
sudo usermod -aG docker $USER
|
||||
|
||||
# Docker Compose 설치
|
||||
sudo apt install -y docker-compose-plugin
|
||||
```
|
||||
|
||||
설치 후 로그아웃했다가 다시 로그인하거나 다음 명령을 실행하여 그룹 변경사항을 적용합니다:
|
||||
```bash
|
||||
newgrp docker
|
||||
```
|
||||
|
||||
### 2.3 Supabase 설치
|
||||
|
||||
1. Supabase 프로젝트 디렉토리 생성:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/supabase
|
||||
cd ~/supabase
|
||||
```
|
||||
|
||||
2. GitHub에서 Supabase Docker 프로젝트 클론:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/supabase/supabase.git
|
||||
cd supabase/docker
|
||||
```
|
||||
|
||||
3. 환경 변수 설정:
|
||||
|
||||
```bash
|
||||
# .env 파일 편집
|
||||
nano .env
|
||||
```
|
||||
|
||||
다음 중요 변수들을 설정합니다. 예시는 적자 탈출 가계부 프로젝트의 실제 설정입니다 (실제 사용 시 보안키는 변경하세요):
|
||||
|
||||
```bash
|
||||
# Supabase 설정
|
||||
POSTGRES_PASSWORD=zellyy_finance_secure_password_2025
|
||||
JWT_SECRET=$(openssl rand -base64 32)
|
||||
ANON_KEY=$(openssl rand -base64 32)
|
||||
SERVICE_ROLE_KEY=$(openssl rand -base64 32)
|
||||
|
||||
# API 및 사이트 URL
|
||||
SITE_URL=https://your-domain.com
|
||||
API_EXTERNAL_URL=https://your-domain.com
|
||||
SUPABASE_PUBLIC_URL=https://your-domain.com
|
||||
STUDIO_PORT=3000
|
||||
|
||||
# 대시보드 인증
|
||||
DASHBOARD_USERNAME=your_username
|
||||
DASHBOARD_PASSWORD=your_secure_dashboard_password
|
||||
|
||||
# PostgreSQL 설정
|
||||
POSTGRES_HOST=db
|
||||
POSTGRES_DB=postgres
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_USER=postgres
|
||||
|
||||
# Kong 설정
|
||||
KONG_HTTP_PORT=8000
|
||||
KONG_HTTPS_PORT=8443
|
||||
|
||||
# 인증 설정
|
||||
JWT_EXPIRY=3600
|
||||
ENABLE_EMAIL_SIGNUP=true
|
||||
ENABLE_EMAIL_AUTOCONFIRM=true
|
||||
ENABLE_PHONE_SIGNUP=true
|
||||
ENABLE_PHONE_AUTOCONFIRM=true
|
||||
DISABLE_SIGNUP=false
|
||||
ENABLE_ANONYMOUS_USERS=false
|
||||
GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=false
|
||||
|
||||
# 이메일 설정 (필요한 경우)
|
||||
SMTP_ADMIN_EMAIL=admin@example.com
|
||||
SMTP_HOST=mail.example.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=smtp_user
|
||||
SMTP_PASS=smtp_password
|
||||
SMTP_SENDER_NAME=Zellyy Finance
|
||||
|
||||
# URL 경로 설정
|
||||
MAILER_URLPATHS_CONFIRMATION=/auth/v1/verify
|
||||
MAILER_URLPATHS_INVITE=/auth/v1/verify
|
||||
MAILER_URLPATHS_RECOVERY=/auth/v1/verify
|
||||
MAILER_URLPATHS_EMAIL_CHANGE=/auth/v1/verify
|
||||
|
||||
# 추가 설정
|
||||
PGRST_DB_SCHEMAS=public,storage,graphql_public
|
||||
SECRET_KEY_BASE=$(openssl rand -base64 32)
|
||||
STUDIO_DEFAULT_ORGANIZATION=Default Organization
|
||||
STUDIO_DEFAULT_PROJECT=Default Project
|
||||
IMGPROXY_ENABLE_WEBP_DETECTION=true
|
||||
FUNCTIONS_VERIFY_JWT=true
|
||||
POOLER_TENANT_ID=tenant1
|
||||
POOLER_DEFAULT_POOL_SIZE=20
|
||||
POOLER_MAX_CLIENT_CONN=100
|
||||
VAULT_ENC_KEY=$(openssl rand -base64 32)
|
||||
POOLER_PROXY_PORT_TRANSACTION=6543
|
||||
```
|
||||
|
||||
4. Supabase 시작:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
이 명령은 모든 Supabase 서비스(PostgreSQL, Kong, GoTrue, PostgREST, Storage 등)를 백그라운드에서 시작합니다.
|
||||
|
||||
### 2.4 SSL/TLS 설정
|
||||
|
||||
프로덕션 환경에서는 HTTPS를 사용하는 것이 필수적입니다. Nginx와 Let's Encrypt를 사용하여 SSL을 설정하세요:
|
||||
|
||||
1. Nginx 및 Certbot 설치:
|
||||
|
||||
```bash
|
||||
sudo apt install -y nginx certbot python3-certbot-nginx
|
||||
```
|
||||
|
||||
2. Let's Encrypt를 통한 SSL 인증서 발급:
|
||||
|
||||
```bash
|
||||
sudo certbot --nginx -d your-domain.com
|
||||
```
|
||||
|
||||
3. Nginx 설정 파일 생성:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/nginx/sites-available/default
|
||||
```
|
||||
|
||||
다음 내용을 추가합니다:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name your-domain.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
|
||||
|
||||
# Supabase Studio
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
}
|
||||
|
||||
# Supabase API (Kong)
|
||||
location /rest/v1/ {
|
||||
proxy_pass http://localhost:8000/rest/v1/;
|
||||
}
|
||||
|
||||
# Supabase Auth
|
||||
location /auth/v1/ {
|
||||
proxy_pass http://localhost:8000/auth/v1/;
|
||||
}
|
||||
|
||||
# Supabase Storage
|
||||
location /storage/v1/ {
|
||||
proxy_pass http://localhost:8000/storage/v1/;
|
||||
}
|
||||
|
||||
# Supabase Realtime
|
||||
location /realtime/v1/ {
|
||||
proxy_pass http://localhost:8000/realtime/v1/;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. Nginx 설정 테스트 및 재시작:
|
||||
|
||||
```bash
|
||||
sudo nginx -t
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
※ 주의: Nginx 설정에서 `proxy_set_header` 지시문을 사용할 때 문법에 주의하세요. 필요한 경우 더 복잡한 헤더 설정을 사용할 수 있지만, 기본 설정으로도 충분히 작동합니다.
|
||||
|
||||
### 2.5 방화벽 설정
|
||||
|
||||
필요한 포트만 열도록 방화벽을 설정합니다:
|
||||
|
||||
```bash
|
||||
sudo apt install -y ufw
|
||||
sudo ufw allow ssh
|
||||
sudo ufw allow http
|
||||
sudo ufw allow https
|
||||
sudo ufw enable
|
||||
```
|
||||
|
||||
### 2.6 Supabase 관리
|
||||
|
||||
Supabase 서비스 관리를 위한 기본 명령어:
|
||||
|
||||
```bash
|
||||
# 서비스 상태 확인
|
||||
cd ~/supabase
|
||||
docker compose ps
|
||||
|
||||
# 로그 확인
|
||||
docker compose logs -f
|
||||
|
||||
# 서비스 중지
|
||||
docker compose down
|
||||
|
||||
# 서비스 시작
|
||||
docker compose up -d
|
||||
|
||||
# 서비스 재시작
|
||||
docker compose restart
|
||||
```
|
||||
|
||||
### 2.7 백업 설정
|
||||
|
||||
정기적인 데이터베이스 백업을 설정하는 것이 좋습니다:
|
||||
|
||||
1. 백업 스크립트 생성:
|
||||
|
||||
```bash
|
||||
nano ~/backup-supabase.sh
|
||||
```
|
||||
|
||||
다음 내용을 추가합니다:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
BACKUP_DIR=~/supabase-backups
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# PostgreSQL 백업
|
||||
cd ~/supabase
|
||||
docker compose exec db pg_dump -U postgres -d postgres > $BACKUP_DIR/supabase_$TIMESTAMP.sql
|
||||
|
||||
# 오래된 백업 삭제 (30일 이상)
|
||||
find $BACKUP_DIR -name "supabase_*.sql" -type f -mtime +30 -delete
|
||||
```
|
||||
|
||||
2. 스크립트에 실행 권한 부여:
|
||||
|
||||
```bash
|
||||
chmod +x ~/backup-supabase.sh
|
||||
```
|
||||
|
||||
3. Cron 작업 설정 (매일 백업):
|
||||
|
||||
```bash
|
||||
crontab -e
|
||||
```
|
||||
|
||||
다음 줄을 추가합니다:
|
||||
|
||||
```
|
||||
0 2 * * * ~/backup-supabase.sh
|
||||
```
|
||||
|
||||
### 2.8 모니터링 설정 (선택 사항)
|
||||
|
||||
서버 상태 모니터링을 위해 Prometheus와 Grafana를 설정할 수 있습니다:
|
||||
|
||||
```bash
|
||||
# Prometheus와 Grafana 설치를 위한 Docker Compose 파일 다운로드
|
||||
mkdir -p ~/monitoring
|
||||
cd ~/monitoring
|
||||
curl -L https://raw.githubusercontent.com/stefanprodan/dockprom/master/docker-compose.yml -o docker-compose.yml
|
||||
|
||||
# 서비스 시작
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Grafana는 기본적으로 http://your-server-ip:3000에서 접근할 수 있으며, 기본 사용자 이름과 비밀번호는 admin/admin입니다.
|
||||
|
||||
## 3. 데이터베이스 설정
|
||||
|
||||
### 3.1 데이터베이스 접속
|
||||
|
||||
Supabase 설치 후 PostgreSQL 데이터베이스에 직접 접속할 수 있습니다:
|
||||
|
||||
```bash
|
||||
cd ~/supabase
|
||||
docker compose exec db psql -U postgres
|
||||
```
|
||||
|
||||
### 3.2 테이블 생성 예시
|
||||
|
||||
다음과 같이 SQL 명령을 사용하여 테이블을 생성할 수 있습니다:
|
||||
|
||||
```sql
|
||||
-- 사용자 테이블 생성
|
||||
CREATE TABLE users (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
full_name TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- RLS(Row Level Security) 정책 설정
|
||||
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "사용자는 자신의 데이터만 볼 수 있음" ON users
|
||||
FOR SELECT USING (auth.uid() = id);
|
||||
CREATE POLICY "사용자는 자신의 데이터만 수정할 수 있음" ON users
|
||||
FOR UPDATE USING (auth.uid() = id);
|
||||
```
|
||||
|
||||
## 4. 인증 설정
|
||||
|
||||
### 4.1 이메일/비밀번호 인증 설정
|
||||
|
||||
자체 호스팅된 Supabase에서 이메일/비밀번호 인증을 설정하려면:
|
||||
|
||||
1. `.env` 파일에서 이메일 설정을 구성합니다:
|
||||
```
|
||||
SMTP_HOST=your-smtp-host
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your-smtp-user
|
||||
SMTP_PASS=your-smtp-password
|
||||
SMTP_SENDER_NAME=Zellyy Finance
|
||||
```
|
||||
|
||||
2. 변경 후 서비스를 재시작합니다:
|
||||
```bash
|
||||
cd ~/supabase
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 4.2 소셜 로그인 설정 (선택 사항)
|
||||
|
||||
소셜 로그인을 추가하려면 각 제공업체의 설정을 `.env` 파일에 추가해야 합니다:
|
||||
|
||||
```
|
||||
# Google OAuth
|
||||
GOTRUE_EXTERNAL_GOOGLE_ENABLED=true
|
||||
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID=your-google-client-id
|
||||
GOTRUE_EXTERNAL_GOOGLE_SECRET=your-google-client-secret
|
||||
GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI=https://your-domain.com/auth/v1/callback
|
||||
|
||||
# GitHub OAuth
|
||||
GOTRUE_EXTERNAL_GITHUB_ENABLED=true
|
||||
GOTRUE_EXTERNAL_GITHUB_CLIENT_ID=your-github-client-id
|
||||
GOTRUE_EXTERNAL_GITHUB_SECRET=your-github-client-secret
|
||||
GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI=https://your-domain.com/auth/v1/callback
|
||||
```
|
||||
|
||||
## 5. 스토리지 설정
|
||||
|
||||
영수증 이미지 등을 저장하기 위한 스토리지 버킷을 설정합니다:
|
||||
|
||||
### 5.1 스토리지 버킷 생성
|
||||
|
||||
SQL을 사용하여 스토리지 버킷을 생성할 수 있습니다:
|
||||
|
||||
```sql
|
||||
-- PostgreSQL에 접속
|
||||
cd ~/supabase
|
||||
docker compose exec db psql -U postgres
|
||||
|
||||
-- 스토리지 버킷 생성
|
||||
INSERT INTO storage.buckets (id, name, public)
|
||||
VALUES ('receipts', 'receipts', false);
|
||||
```
|
||||
|
||||
### 5.2 스토리지 보안 정책 설정
|
||||
|
||||
스토리지 버킷에 대한 접근 권한을 설정합니다:
|
||||
|
||||
```sql
|
||||
-- 사용자는 자신의 파일만 업로드할 수 있음
|
||||
CREATE POLICY "사용자는 자신의 파일만 업로드할 수 있음" ON storage.objects
|
||||
FOR INSERT WITH CHECK (auth.uid() = owner);
|
||||
|
||||
-- 사용자는 자신의 파일만 조회할 수 있음
|
||||
CREATE POLICY "사용자는 자신의 파일만 조회할 수 있음" ON storage.objects
|
||||
FOR SELECT USING (auth.uid() = owner);
|
||||
|
||||
-- 사용자는 자신의 파일만 삭제할 수 있음
|
||||
CREATE POLICY "사용자는 자신의 파일만 삭제할 수 있음" ON storage.objects
|
||||
FOR DELETE USING (auth.uid() = owner);
|
||||
```
|
||||
|
||||
## 6. API 및 클라이언트 라이브러리
|
||||
|
||||
### 6.1 Supabase 클라이언트 설정 (Flutter)
|
||||
|
||||
Flutter 프로젝트에서 자체 호스팅된 Supabase를 사용하기 위한 설정:
|
||||
|
||||
1. 필요한 패키지 추가:
|
||||
|
||||
```yaml
|
||||
# pubspec.yaml
|
||||
dependencies:
|
||||
supabase_flutter: ^1.10.14
|
||||
```
|
||||
|
||||
2. 초기화 코드:
|
||||
|
||||
```dart
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await Supabase.initialize(
|
||||
url: 'https://your-domain.com', // 자체 호스팅 서버 URL
|
||||
anonKey: 'your-anon-key', // .env 파일에서 설정한 ANON_KEY
|
||||
);
|
||||
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
// 어디서나 클라이언트에 접근
|
||||
final supabase = Supabase.instance.client;
|
||||
```
|
||||
|
||||
### 6.2 기본 API 사용 예시
|
||||
|
||||
```dart
|
||||
// 사용자 등록
|
||||
Future<void> signUp(String email, String password) async {
|
||||
await supabase.auth.signUp(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
}
|
||||
|
||||
// 로그인
|
||||
Future<void> signIn(String email, String password) async {
|
||||
await supabase.auth.signInWithPassword(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
}
|
||||
|
||||
// 데이터 조회
|
||||
Future<List<Map<String, dynamic>>> getExpenses() async {
|
||||
final response = await supabase
|
||||
.from('expenses')
|
||||
.select()
|
||||
.eq('user_id', supabase.auth.currentUser!.id);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// 데이터 추가
|
||||
Future<void> addExpense(Map<String, dynamic> expenseData) async {
|
||||
await supabase
|
||||
.from('expenses')
|
||||
.insert(expenseData);
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 유지 관리 및 모니터링
|
||||
|
||||
### 7.1 서비스 관리 명령어
|
||||
|
||||
Supabase 서비스 관리를 위한 기본 명령어:
|
||||
|
||||
```bash
|
||||
# 서비스 상태 확인
|
||||
cd ~/supabase
|
||||
docker compose ps
|
||||
|
||||
# 로그 확인
|
||||
docker compose logs -f
|
||||
|
||||
# 서비스 중지
|
||||
docker compose down
|
||||
|
||||
# 서비스 시작
|
||||
docker compose up -d
|
||||
|
||||
# 서비스 재시작
|
||||
docker compose restart
|
||||
|
||||
# 특정 서비스만 재시작 (예: PostgreSQL)
|
||||
docker compose restart db
|
||||
```
|
||||
|
||||
### 7.2 데이터베이스 백업 및 복원
|
||||
|
||||
#### 백업
|
||||
|
||||
```bash
|
||||
cd ~/supabase
|
||||
docker compose exec db pg_dump -U postgres -d postgres > backup_$(date +%Y%m%d).sql
|
||||
```
|
||||
|
||||
#### 복원
|
||||
|
||||
```bash
|
||||
cd ~/supabase
|
||||
cat backup_file.sql | docker compose exec -T db psql -U postgres -d postgres
|
||||
```
|
||||
|
||||
### 7.3 업데이트
|
||||
|
||||
Supabase를 최신 버전으로 업데이트하려면:
|
||||
|
||||
```bash
|
||||
cd ~/supabase
|
||||
docker compose pull
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## 8. 보안 모범 사례
|
||||
|
||||
1. **API 키 보호**: `service_role` 키는 절대 클라이언트 코드에 포함하지 마세요.
|
||||
2. **RLS(Row Level Security)**: 모든 테이블에 RLS 정책을 설정하세요.
|
||||
3. **환경 변수**: API 키와 같은 민감한 정보는 환경 변수로 관리하세요.
|
||||
4. **정기적인 비밀번호 변경**: 데이터베이스 비밀번호를 정기적으로 변경하세요.
|
||||
5. **최소 권한 원칙**: 각 사용자와 서비스에 필요한 최소한의 권한만 부여하세요.
|
||||
6. **방화벽 설정**: 필요한 포트만 개방하세요.
|
||||
7. **정기적인 업데이트**: 보안 패치를 위해 정기적으로 시스템을 업데이트하세요.
|
||||
|
||||
## 9. 문제 해결
|
||||
|
||||
### 9.1 문제 해결
|
||||
|
||||
1. **데이터베이스 연결 오류**: `.env` 파일의 POSTGRES_PASSWORD 및 관련 데이터베이스 설정을 확인하세요.
|
||||
2. **SSL 인증서 오류**: Certbot 설정 및 Nginx 구성을 확인하세요.
|
||||
3. **스토리지 문제**: `.env` 파일의 STORAGE_BACKEND 설정을 확인하세요.
|
||||
4. **인증 오류**: `.env` 파일의 JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY 설정을 확인하세요.
|
||||
5. **Pooler 서비스 재시작 문제**:
|
||||
- 문제: `supabase-pooler` 서비스가 계속 재시작되는 문제
|
||||
- 해결: `.env` 파일에 `SUPAVISOR_ENCRYPTION_KEY` 변수를 추가하고 유효한 암호화 키를 설정
|
||||
- 추가 설정: `POOLER_ENABLE_TENANT_SHARDING=true` 옵션 추가로 성능 향상
|
||||
|
||||
6. **Realtime 서비스 Unhealthy 상태**:
|
||||
- 문제: `realtime-dev.supabase-realtime` 서비스가 Unhealthy 상태로 표시됨
|
||||
- 참고: 기본 CRUD 작업에는 영향을 주지 않으며, 실시간 기능을 사용하지 않는 경우 무시 가능
|
||||
- 디버깅: `docker logs realtime-dev.supabase-realtime`로 구체적인 오류 확인 가능
|
||||
|
||||
7. **Nginx 리버스 프록시 502 오류**:
|
||||
- 문제: Nginx에서 502 Bad Gateway 오류 발생
|
||||
- 해결:
|
||||
- Supabase Studio 컨테이너에 포트 매핑 추가 (3000:3000)
|
||||
- Nginx 설정에서 프록시 패스 설정 올바르게 구성
|
||||
- Docker 컨테이너와 Nginx 모두 재시작
|
||||
|
||||
8. **호스트 네임 설정 문제**:
|
||||
- 문제: `SITE_URL` 설정이 올바르지 않아 인증 콜백 오류 발생
|
||||
- 해결: `.env` 파일에서 `SITE_URL`을 정확한 도메인으로 설정 (https://a11.ism.kr)
|
||||
|
||||
### 9.2 지원 받기
|
||||
|
||||
1. [Supabase 문서](https://supabase.com/docs)
|
||||
2. [GitHub 이슈](https://github.com/supabase/supabase/issues)
|
||||
3. [Discord 커뮤니티](https://discord.supabase.com)
|
||||
|
||||
## 10. 다음 단계
|
||||
|
||||
Supabase 설치가 완료되면 다음 단계로 진행하세요:
|
||||
|
||||
1. 데이터베이스 스키마 구현
|
||||
2. API 엔드포인트 개발
|
||||
3. 인증 시스템 테스트
|
||||
4. 프론트엔드 연동 준비
|
||||
|
||||
## 3. 데이터베이스 설정
|
||||
|
||||
### 3.3 Supabase Studio 접근
|
||||
|
||||
Supabase Studio는 데이터베이스 관리, API 탐색, 인증 설정 등을 위한 웹 기반 관리 인터페이스입니다.
|
||||
|
||||
```
|
||||
https://your-domain.com
|
||||
```
|
||||
|
||||
자체 호스팅 환경에서는 Supabase Studio에 별도의 로그인 없이 바로 접근할 수 있습니다. 이는 기본 설정이며, 추가적인 보안이 필요한 경우 Nginx 수준에서 기본 인증을 구성하는 것이 좋습니다.
|
||||
|
||||
> 참고: 클라우드 호스팅 Supabase와 달리, 자체 호스팅 환경에서는 Studio 접근에 대한 인증이 기본적으로 활성화되어 있지 않습니다. 프로덕션 환경에서는 추가 보안 조치를 고려하세요.
|
||||
|
||||
#### Nginx를 통한 기본 인증 추가 (선택 사항)
|
||||
|
||||
보안 강화를 위해 Nginx 설정에 기본 인증을 추가할 수 있습니다:
|
||||
|
||||
```bash
|
||||
# htpasswd 파일 생성
|
||||
sudo apt install apache2-utils
|
||||
sudo htpasswd -c /etc/nginx/.htpasswd your-username
|
||||
```
|
||||
|
||||
Nginx 설정 파일에 다음을 추가:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
# 기존 설정...
|
||||
|
||||
location / {
|
||||
# 기본 인증 추가
|
||||
auth_basic "Restricted Access";
|
||||
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||
|
||||
# 기존 프록시 설정...
|
||||
proxy_pass http://localhost:3000;
|
||||
# ...
|
||||
}
|
||||
|
||||
# 다른 위치 블록...
|
||||
}
|
||||
38
docs/02_기술_문서/Supabase_인증_정보.md
Normal file
38
docs/02_기술_문서/Supabase_인증_정보.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Supabase 인증 정보
|
||||
|
||||
## 서버 접속 정보
|
||||
- **서버 주소**: a11.ism.kr
|
||||
- **접속 방법**: SSH 키 인증
|
||||
- **사용자**: ism-admin
|
||||
- **공개 키**: `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHXL6O0/Ydj2Tmsu3kjo0ypVPUCjNV/bxgFPMjM1dzHD ism-admin`
|
||||
- **개인 키 파일**: `~/.ssh/id_ed25519_ism`
|
||||
- **간편 접속 명령어**: `ssh a11` (SSH config에 설정됨)
|
||||
|
||||
## Supabase 환경 변수
|
||||
- **SITE_URL**: https://a11.ism.kr
|
||||
- **API_EXTERNAL_URL**: https://a11.ism.kr
|
||||
- **POSTGRES_PASSWORD**: zellyy_finance_secure_password_2025
|
||||
- **DASHBOARD_USERNAME**: hansoo
|
||||
- **DASHBOARD_PASSWORD**: zellyy_secure_dashboard_password_2025
|
||||
|
||||
## Nginx 인증서 정보
|
||||
- **인증서 발급 방법**: Let's Encrypt (Certbot)
|
||||
- **인증서 갱신 주기**: 자동 (90일)
|
||||
- **인증서 저장 위치**: /etc/letsencrypt/live/a11.ism.kr/
|
||||
|
||||
## API 키 정보
|
||||
- **ANON_KEY**: (설치 후 자동 생성됨 - 여기에 기록 필요)
|
||||
- **SERVICE_ROLE_KEY**: (설치 후 자동 생성됨 - 여기에 기록 필요)
|
||||
- **JWT_SECRET**: (설치 후 자동 생성됨 - 여기에 기록 필요)
|
||||
|
||||
## 데이터베이스 접속 정보
|
||||
- **호스트**: localhost
|
||||
- **포트**: 5432
|
||||
- **데이터베이스**: postgres
|
||||
- **사용자**: postgres
|
||||
- **비밀번호**: zellyy_finance_secure_password_2025
|
||||
|
||||
## 방화벽 설정
|
||||
- 허용 포트: 80(HTTP), 443(HTTPS), 22(SSH)
|
||||
|
||||
> **중요**: 이 문서에는 민감한 보안 정보가 포함되어 있습니다. 안전하게 보관하고 공개 저장소에 업로드하지 마세요.
|
||||
25
docs/02_기술_문서/index.md
Normal file
25
docs/02_기술_문서/index.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# 기술 문서
|
||||
|
||||
이 폴더에는 적자 탈출 가계부 프로젝트의 기술적 구현에 관한 문서가 포함되어 있습니다.
|
||||
|
||||
## 문서 목록
|
||||
|
||||
1. [[데이터 모델 설계]] - 데이터 모델 설계 문서
|
||||
2. [[ERD 다이어그램]] - 엔티티 관계 다이어그램
|
||||
3. [[데이터베이스 스키마 스크립트.sql]] - 데이터베이스 스키마 SQL 스크립트
|
||||
4. [[Supabase 설정 가이드]] - Supabase 자체 호스팅 설정 가이드
|
||||
5. [[Supabase 인증 정보]] - Supabase 인증 및 보안 정보 (비공개)
|
||||
6. [[Nginx Supabase 설치 가이드]] - Nginx와 Supabase 설치 및 설정 방법
|
||||
|
||||
## 기술 스택 요약
|
||||
|
||||
적자 탈출 가계부 프로젝트는 다음과 같은 기술 스택을 사용합니다:
|
||||
|
||||
- **프론트엔드**: Flutter (크로스 플랫폼 모바일 앱)
|
||||
- **백엔드**: Supabase (자체 호스팅)
|
||||
- **데이터베이스**: PostgreSQL (Supabase 내장)
|
||||
- **인증**: Supabase Auth
|
||||
- **스토리지**: Supabase Storage
|
||||
- **배포 환경**: Debian 12 서버
|
||||
|
||||
이 문서들은 프로젝트의 기술적 구현에 필요한 상세 정보를 제공하며, 개발자가 시스템을 이해하고 구현하는 데 도움을 줍니다.
|
||||
42
docs/02_기술_문서/기술문서_관리.md
Normal file
42
docs/02_기술_문서/기술문서_관리.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 기술 문서 관리
|
||||
|
||||
## 개요
|
||||
Zellyy Finance 프로젝트의 기술 문서는 코드와 함께 Gitea 저장소에서 관리됩니다. 이 문서는 기술 문서의 위치와 관리 방법에 대한 정보를 제공합니다.
|
||||
|
||||
## 기술 문서 위치
|
||||
- **소스 코드 저장소**: `/Users/hansoo./Dev/Zellyy_Finance/docs/`
|
||||
- **Git 저장소 URL**: [Zellyy_Finance Git Repository](https://gitea.com/hansoo/Zellyy_Finance)
|
||||
|
||||
## 문서 관리 원칙
|
||||
- **단일 소스 원칙**: 모든 코드 관련 기술 문서는 Gitea에서만 관리합니다.
|
||||
- **옵시디언 참조 원칙**: 옵시디언에는 Gitea의 문서를 참조하는 링크만 저장합니다.
|
||||
|
||||
## 주요 기술 문서 목록
|
||||
|
||||
### API 문서
|
||||
- [API 엔드포인트](/Users/hansoo./Dev/Zellyy_Finance/docs/api/api_endpoints.md): Zellyy Finance API의 모든 엔드포인트 설명
|
||||
- [API 인증](/Users/hansoo./Dev/Zellyy_Finance/docs/api/authentication.md): API 인증 방법 설명
|
||||
|
||||
### 데이터베이스 문서
|
||||
- [데이터베이스 스키마](/Users/hansoo./Dev/Zellyy_Finance/docs/database/schema.md): 데이터베이스 테이블 구조 및 관계 설명
|
||||
|
||||
### 설계 문서
|
||||
- [아키텍처 설계](/Users/hansoo./Dev/Zellyy_Finance/docs/design/architecture.md): 시스템 아키텍처 설명
|
||||
|
||||
### 요구사항 문서
|
||||
- [기능 요구사항](/Users/hansoo./Dev/Zellyy_Finance/docs/requirements/features.md): 시스템 기능 요구사항 설명
|
||||
|
||||
## 문서 업데이트 방법
|
||||
기술 문서를 업데이트해야 할 경우, 다음 절차를 따릅니다:
|
||||
|
||||
1. Gitea 저장소의 문서만 수정합니다.
|
||||
2. 변경 사항을 커밋하고 푸시합니다.
|
||||
3. 필요한 경우 이 문서에 중요한 변경 사항을 기록합니다.
|
||||
|
||||
## 주의사항
|
||||
- 옵시디언에는 코드나 코드 관련 기술 문서를 저장하지 않는 원칙을 따릅니다.
|
||||
- 기술 문서와 코드의 일관성을 유지하기 위해 항상 Gitea의 문서를 참조하세요.
|
||||
|
||||
## 최근 업데이트
|
||||
- 2025-02-27: 기술 문서 관리 원칙 수립 (Gitea에서만 관리)
|
||||
- 2025-02-27: 기술 문서 참조 링크 추가
|
||||
271
docs/02_기술_문서/데이터_모델_설계.md
Normal file
271
docs/02_기술_문서/데이터_모델_설계.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# 적자 탈출 가계부 - 데이터 모델 설계
|
||||
|
||||
## 1. 핵심 데이터 엔티티
|
||||
|
||||
### 1.1 사용자(Users) 테이블
|
||||
|
||||
```
|
||||
users
|
||||
- id: UUID (PK)
|
||||
- email: STRING (UNIQUE)
|
||||
- password_hash: STRING
|
||||
- name: STRING
|
||||
- profile_image: STRING (nullable)
|
||||
- created_at: TIMESTAMP
|
||||
- updated_at: TIMESTAMP
|
||||
- last_login_at: TIMESTAMP (nullable)
|
||||
- notification_settings: JSON
|
||||
- theme_preference: STRING (default: 'light')
|
||||
- is_active: BOOLEAN (default: true)
|
||||
```
|
||||
|
||||
### 1.2 카드(Cards) 테이블
|
||||
|
||||
```
|
||||
cards
|
||||
- id: UUID (PK)
|
||||
- user_id: UUID (FK -> users.id)
|
||||
- name: STRING
|
||||
- card_type: ENUM ('credit', 'debit', 'prepaid')
|
||||
- card_number_last4: STRING
|
||||
- issuer: STRING
|
||||
- color: STRING (for UI)
|
||||
- payment_day: INTEGER (1-31)
|
||||
- monthly_limit: DECIMAL
|
||||
- is_active: BOOLEAN (default: true)
|
||||
- created_at: TIMESTAMP
|
||||
- updated_at: TIMESTAMP
|
||||
```
|
||||
|
||||
### 1.3 카테고리(Categories) 테이블
|
||||
|
||||
```
|
||||
categories
|
||||
- id: UUID (PK)
|
||||
- name: STRING
|
||||
- icon: STRING
|
||||
- color: STRING
|
||||
- parent_id: UUID (FK -> categories.id, nullable)
|
||||
- is_system: BOOLEAN (default: false)
|
||||
- is_active: BOOLEAN (default: true)
|
||||
- created_at: TIMESTAMP
|
||||
- updated_at: TIMESTAMP
|
||||
```
|
||||
|
||||
### 1.4 지출(Expenses) 테이블
|
||||
|
||||
```
|
||||
expenses
|
||||
- id: UUID (PK)
|
||||
- user_id: UUID (FK -> users.id)
|
||||
- card_id: UUID (FK -> cards.id)
|
||||
- category_id: UUID (FK -> categories.id)
|
||||
- amount: DECIMAL
|
||||
- merchant: STRING
|
||||
- transaction_date: TIMESTAMP
|
||||
- description: TEXT (nullable)
|
||||
- location: GEOGRAPHY (nullable)
|
||||
- is_recurring: BOOLEAN (default: false)
|
||||
- receipt_image: STRING (nullable)
|
||||
- tags: STRING[] (nullable)
|
||||
- created_at: TIMESTAMP
|
||||
- updated_at: TIMESTAMP
|
||||
```
|
||||
|
||||
### 1.5 한도(Limits) 테이블
|
||||
|
||||
```
|
||||
limits
|
||||
- id: UUID (PK)
|
||||
- user_id: UUID (FK -> users.id)
|
||||
- card_id: UUID (FK -> cards.id, nullable)
|
||||
- category_id: UUID (FK -> categories.id, nullable)
|
||||
- amount: DECIMAL
|
||||
- period: ENUM ('daily', 'weekly', 'monthly')
|
||||
- start_date: DATE
|
||||
- end_date: DATE (nullable)
|
||||
- is_active: BOOLEAN (default: true)
|
||||
- created_at: TIMESTAMP
|
||||
- updated_at: TIMESTAMP
|
||||
```
|
||||
|
||||
### 1.6 알림(Notifications) 테이블
|
||||
|
||||
```
|
||||
notifications
|
||||
- id: UUID (PK)
|
||||
- user_id: UUID (FK -> users.id)
|
||||
- title: STRING
|
||||
- message: TEXT
|
||||
- type: ENUM ('limit_warning', 'payment_reminder', 'tip', 'system')
|
||||
- related_entity_type: STRING (nullable)
|
||||
- related_entity_id: UUID (nullable)
|
||||
- is_read: BOOLEAN (default: false)
|
||||
- created_at: TIMESTAMP
|
||||
- read_at: TIMESTAMP (nullable)
|
||||
```
|
||||
|
||||
### 1.7 템플릿(Templates) 테이블
|
||||
|
||||
```
|
||||
templates
|
||||
- id: UUID (PK)
|
||||
- user_id: UUID (FK -> users.id)
|
||||
- name: STRING
|
||||
- category_id: UUID (FK -> categories.id)
|
||||
- amount: DECIMAL (nullable)
|
||||
- merchant: STRING (nullable)
|
||||
- description: TEXT (nullable)
|
||||
- is_favorite: BOOLEAN (default: false)
|
||||
- created_at: TIMESTAMP
|
||||
- updated_at: TIMESTAMP
|
||||
```
|
||||
|
||||
## 2. 데이터 관계 및 제약 조건
|
||||
|
||||
### 2.1 테이블 간 관계 (ERD)
|
||||
|
||||
```
|
||||
Users (1) --- (*) Cards
|
||||
Users (1) --- (*) Expenses
|
||||
Users (1) --- (*) Limits
|
||||
Users (1) --- (*) Notifications
|
||||
Users (1) --- (*) Templates
|
||||
|
||||
Cards (1) --- (*) Expenses
|
||||
Cards (1) --- (*) Limits
|
||||
|
||||
Categories (1) --- (*) Expenses
|
||||
Categories (1) --- (*) Limits
|
||||
Categories (1) --- (*) Templates
|
||||
Categories (1) --- (*) Categories (자기 참조: 부모-자식 관계)
|
||||
```
|
||||
|
||||
### 2.2 기본 카테고리 목록
|
||||
|
||||
시스템에서 기본적으로 제공하는 카테고리 목록입니다:
|
||||
|
||||
1. **식비**
|
||||
- 식료품
|
||||
- 외식
|
||||
- 카페/음료
|
||||
|
||||
2. **주거/통신**
|
||||
- 월세/관리비
|
||||
- 전기/수도/가스
|
||||
- 인터넷/통신비
|
||||
- 가구/가전
|
||||
|
||||
3. **교통**
|
||||
- 대중교통
|
||||
- 자동차/유류비
|
||||
- 택시
|
||||
|
||||
4. **쇼핑**
|
||||
- 의류/패션
|
||||
- 화장품/미용
|
||||
- 전자기기
|
||||
- 생활용품
|
||||
|
||||
5. **여가/문화**
|
||||
- 영화/공연
|
||||
- 취미/레저
|
||||
- 여행/숙박
|
||||
- 도서/음악
|
||||
|
||||
6. **의료/건강**
|
||||
- 병원/약국
|
||||
- 운동/피트니스
|
||||
|
||||
7. **교육**
|
||||
- 학비/등록금
|
||||
- 학원/강의
|
||||
- 교재/학습도구
|
||||
|
||||
8. **금융**
|
||||
- 보험
|
||||
- 세금
|
||||
- 대출이자
|
||||
- 투자
|
||||
|
||||
9. **기타**
|
||||
- 기부/후원
|
||||
- 용돈/선물
|
||||
- 기타 지출
|
||||
|
||||
### 2.3 데이터 유효성 검사 규칙
|
||||
|
||||
#### 사용자(Users)
|
||||
- 이메일은 유효한 형식이어야 하며 중복될 수 없음
|
||||
- 비밀번호는 최소 8자 이상, 영문/숫자/특수문자 조합
|
||||
- 이름은 필수 입력 항목
|
||||
|
||||
#### 카드(Cards)
|
||||
- 카드 이름은 필수 입력 항목
|
||||
- 월 한도는 0보다 커야 함
|
||||
- 결제일은 1-31 사이의 값이어야 함
|
||||
|
||||
#### 지출(Expenses)
|
||||
- 금액은 0보다 커야 함
|
||||
- 거래 날짜는 현재 날짜보다 미래일 수 없음
|
||||
- 카테고리는 필수 선택 항목
|
||||
|
||||
#### 한도(Limits)
|
||||
- 한도 금액은 0보다 커야 함
|
||||
- 카드 ID와 카테고리 ID는 둘 다 null이거나, 둘 중 하나만 지정되어야 함
|
||||
- 시작 날짜는 필수 입력 항목
|
||||
|
||||
#### 템플릿(Templates)
|
||||
- 템플릿 이름은 필수 입력 항목
|
||||
- 카테고리는 필수 선택 항목
|
||||
|
||||
## 3. 데이터베이스 인덱스 설계
|
||||
|
||||
성능 최적화를 위한 인덱스 설계:
|
||||
|
||||
```
|
||||
users: (email)
|
||||
cards: (user_id, is_active)
|
||||
expenses: (user_id, card_id), (user_id, category_id), (user_id, transaction_date), (is_recurring)
|
||||
limits: (user_id, card_id), (user_id, category_id), (user_id, is_active)
|
||||
notifications: (user_id, is_read), (user_id, created_at)
|
||||
templates: (user_id, is_favorite)
|
||||
categories: (parent_id), (is_system)
|
||||
```
|
||||
|
||||
## 4. 데이터 마이그레이션 전략
|
||||
|
||||
1. **초기 설정**
|
||||
- 기본 카테고리 데이터 생성
|
||||
- 시스템 설정 데이터 생성
|
||||
|
||||
2. **버전 관리**
|
||||
- 데이터베이스 스키마 버전 관리 시스템 도입
|
||||
- 각 마이그레이션에 고유 ID 부여
|
||||
|
||||
3. **업그레이드 경로**
|
||||
- 이전 버전에서 새 버전으로의 마이그레이션 스크립트 준비
|
||||
- 데이터 무결성 검증 단계 포함
|
||||
|
||||
4. **롤백 전략**
|
||||
- 각 마이그레이션에 대한 롤백 스크립트 준비
|
||||
- 중요 데이터 변경 전 백업 수행
|
||||
|
||||
## 5. 데이터 보안 고려사항
|
||||
|
||||
1. **개인정보 보호**
|
||||
- 사용자 비밀번호는 단방향 해시로 저장 (bcrypt 또는 Argon2)
|
||||
- 카드 번호는 마지막 4자리만 저장
|
||||
- 개인식별정보(PII)는 암호화하여 저장
|
||||
|
||||
2. **접근 제어**
|
||||
- 사용자는 자신의 데이터만 접근 가능
|
||||
- 관리자 권한은 최소 필요 인원에게만 부여
|
||||
|
||||
3. **데이터 백업**
|
||||
- 정기적인 데이터베이스 백업 수행
|
||||
- 백업 데이터 암호화 저장
|
||||
|
||||
4. **감사 로깅**
|
||||
- 중요 데이터 변경에 대한 감사 로그 유지
|
||||
- 비정상적인 접근 패턴 모니터링
|
||||
298
docs/02_기술_문서/데이터베이스_스키마_구현.md
Normal file
298
docs/02_기술_문서/데이터베이스_스키마_구현.md
Normal file
@@ -0,0 +1,298 @@
|
||||
# 적자 탈출 가계부 - 데이터베이스 스키마 구현
|
||||
|
||||
## 1. 개요
|
||||
|
||||
적자 탈출 가계부 애플리케이션의 데이터베이스 스키마 구현에 대한 문서입니다. 이 문서는 Supabase 자체 호스팅 환경에서 PostgreSQL 데이터베이스를 사용하여 구현된 데이터베이스 스키마를 설명합니다.
|
||||
|
||||
## 2. 마이그레이션 파일 구조
|
||||
|
||||
데이터베이스 스키마는 다음과 같은 마이그레이션 파일로 구성되어 있습니다:
|
||||
|
||||
1. `01_users.sql`: 사용자 정보 관리
|
||||
2. `02_categories.sql`: 지출 카테고리 관리
|
||||
3. `03_expenses.sql`: 지출 내역 관리
|
||||
4. `04_budgets.sql`: 예산 관리
|
||||
5. `05_cards.sql`: 카드 정보 관리
|
||||
6. `06_limits.sql`: 지출 한도 관리
|
||||
7. `07_templates.sql`: 템플릿 관리
|
||||
8. `08_notifications.sql`: 알림 시스템
|
||||
9. `09_analysis_settings.sql`: 분석 설정
|
||||
|
||||
각 마이그레이션 파일은 테이블 생성, 인덱스 생성, RLS(Row Level Security) 정책 설정, 관련 함수 및 트리거를 포함합니다.
|
||||
|
||||
## 3. 테이블 구조
|
||||
|
||||
### 3.1 사용자(users) 테이블
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||
);
|
||||
```
|
||||
|
||||
- **주요 기능**: 사용자 정보 저장
|
||||
- **RLS 정책**: 사용자는 자신의 정보만 조회/수정 가능
|
||||
- **관련 트리거**: Supabase Auth와 연동하여 사용자 생성 시 프로필 자동 생성
|
||||
|
||||
### 3.2 카테고리(categories) 테이블
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS categories (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
name VARCHAR(50) NOT NULL,
|
||||
icon VARCHAR(50) NOT NULL,
|
||||
color VARCHAR(20) NOT NULL,
|
||||
parent_id UUID REFERENCES categories(id),
|
||||
is_income BOOLEAN DEFAULT FALSE,
|
||||
is_default BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||
);
|
||||
```
|
||||
|
||||
- **주요 기능**: 지출/수입 카테고리 관리
|
||||
- **RLS 정책**: 모든 사용자가 조회 가능, 수정은 관리자만 가능
|
||||
- **기본 데이터**: 40개 이상의 기본 카테고리 제공 (지출 및 수입 카테고리)
|
||||
- **계층 구조**: 부모-자식 관계를 통한 카테고리 계층화
|
||||
|
||||
### 3.3 지출(expenses) 테이블
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS expenses (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
amount NUMERIC(12,2) NOT NULL,
|
||||
date DATE NOT NULL,
|
||||
category_id UUID REFERENCES categories(id),
|
||||
memo TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||
);
|
||||
```
|
||||
|
||||
- **주요 기능**: 사용자의 지출/수입 내역 저장
|
||||
- **RLS 정책**: 사용자는 자신의 지출 데이터만 접근 가능
|
||||
- **관련 함수**: 월별 지출 합계 조회 함수 제공
|
||||
|
||||
### 3.4 예산(budgets) 테이블
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS budgets (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
amount NUMERIC(12,2) NOT NULL,
|
||||
month INTEGER NOT NULL CHECK (month BETWEEN 1 AND 12),
|
||||
year INTEGER NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
||||
UNIQUE(user_id, month, year)
|
||||
);
|
||||
```
|
||||
|
||||
- **주요 기능**: 사용자의 월별 예산 관리
|
||||
- **RLS 정책**: 사용자는 자신의 예산 데이터만 접근 가능
|
||||
- **관련 함수**: 예산 대비 지출 비율 조회 함수 제공
|
||||
|
||||
### 3.5 카드(cards) 테이블
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS cards (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
card_type VARCHAR(20) NOT NULL CHECK (card_type IN ('credit', 'debit', 'prepaid')),
|
||||
card_number_last4 VARCHAR(4),
|
||||
issuer VARCHAR(50),
|
||||
color VARCHAR(20),
|
||||
payment_day INTEGER CHECK (payment_day BETWEEN 1 AND 31),
|
||||
monthly_limit NUMERIC(12,2),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||
);
|
||||
```
|
||||
|
||||
- **주요 기능**: 사용자의 카드 정보 관리
|
||||
- **RLS 정책**: 사용자는 자신의 카드 데이터만 접근 가능
|
||||
- **관련 함수**: 사용자별 카드 목록 조회 함수 제공
|
||||
|
||||
### 3.6 한도(limits) 테이블
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS limits (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
card_id UUID REFERENCES cards(id) ON DELETE CASCADE,
|
||||
category_id UUID REFERENCES categories(id) ON DELETE SET NULL,
|
||||
amount NUMERIC(12,2) NOT NULL CHECK (amount > 0),
|
||||
period VARCHAR(20) NOT NULL CHECK (period IN ('daily', 'weekly', 'monthly')),
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
||||
CONSTRAINT card_or_category_required CHECK (card_id IS NOT NULL OR category_id IS NOT NULL),
|
||||
CONSTRAINT valid_date_range CHECK (end_date IS NULL OR end_date >= start_date)
|
||||
);
|
||||
```
|
||||
|
||||
- **주요 기능**: 카드 또는 카테고리별 지출 한도 관리
|
||||
- **RLS 정책**: 사용자는 자신의 한도 데이터만 접근 가능
|
||||
- **관련 함수**: 활성화된 한도 조회 및 사용량 계산 함수 제공
|
||||
|
||||
### 3.7 템플릿(templates) 테이블
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS templates (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
card_id UUID REFERENCES cards(id) ON DELETE SET NULL,
|
||||
category_id UUID REFERENCES categories(id) ON DELETE SET NULL,
|
||||
amount NUMERIC(12,2) NOT NULL CHECK (amount > 0),
|
||||
description TEXT,
|
||||
location VARCHAR(255),
|
||||
is_income BOOLEAN DEFAULT FALSE,
|
||||
is_favorite BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
||||
UNIQUE(user_id, name)
|
||||
);
|
||||
```
|
||||
|
||||
- **주요 기능**: 자주 사용하는 지출/수입 템플릿 관리
|
||||
- **RLS 정책**: 사용자는 자신의 템플릿 데이터만 접근 가능
|
||||
- **관련 함수**: 템플릿으로 지출 생성 함수 제공
|
||||
|
||||
### 3.8 알림(notifications) 테이블
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS notifications (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
title VARCHAR(100) NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
type VARCHAR(20) NOT NULL CHECK (type IN ('limit_warning', 'payment_due', 'tip', 'system')),
|
||||
related_id UUID,
|
||||
related_type VARCHAR(50),
|
||||
is_read BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
||||
read_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
```
|
||||
|
||||
- **주요 기능**: 사용자 알림 관리 (한도 경고, 결제일 알림 등)
|
||||
- **RLS 정책**: 사용자는 자신의 알림 데이터만 접근 가능
|
||||
- **관련 트리거**: 지출 한도 초과 시 자동 알림 생성
|
||||
|
||||
### 3.9 분석 설정(analysis_settings) 테이블
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS analysis_settings (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
budget_period VARCHAR(20) DEFAULT 'monthly' CHECK (budget_period IN ('daily', 'weekly', 'monthly')),
|
||||
saving_goal NUMERIC(12,2),
|
||||
analysis_period INTEGER DEFAULT 3 CHECK (analysis_period > 0),
|
||||
notification_frequency VARCHAR(20) DEFAULT 'weekly' CHECK (notification_frequency IN ('daily', 'weekly', 'monthly', 'none')),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
||||
UNIQUE(user_id)
|
||||
);
|
||||
```
|
||||
|
||||
- **주요 기능**: 사용자별 분석 설정 관리
|
||||
- **RLS 정책**: 사용자는 자신의 분석 설정 데이터만 접근 가능
|
||||
- **관련 트리거**: 사용자 생성 시 기본 분석 설정 자동 생성
|
||||
- **관련 함수**: 지출 분석 및 추세 분석 함수 제공
|
||||
|
||||
## 4. Row Level Security(RLS) 정책
|
||||
|
||||
모든 테이블에는 Row Level Security 정책이 적용되어 있습니다. 이를 통해 사용자는 자신의 데이터만 접근할 수 있으며, 다른 사용자의 데이터에는 접근할 수 없습니다.
|
||||
|
||||
기본적인 RLS 정책 패턴:
|
||||
|
||||
```sql
|
||||
-- 테이블에 RLS 활성화
|
||||
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- 조회 정책
|
||||
CREATE POLICY "select_policy" ON table_name
|
||||
FOR SELECT USING (auth.uid() = user_id);
|
||||
|
||||
-- 삽입 정책
|
||||
CREATE POLICY "insert_policy" ON table_name
|
||||
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- 수정 정책
|
||||
CREATE POLICY "update_policy" ON table_name
|
||||
FOR UPDATE USING (auth.uid() = user_id);
|
||||
|
||||
-- 삭제 정책
|
||||
CREATE POLICY "delete_policy" ON table_name
|
||||
FOR DELETE USING (auth.uid() = user_id);
|
||||
```
|
||||
|
||||
## 5. 인덱스
|
||||
|
||||
성능 최적화를 위해 각 테이블에 적절한 인덱스가 생성되어 있습니다:
|
||||
|
||||
- 외래 키 컬럼에 대한 인덱스
|
||||
- 자주 조회되는 컬럼에 대한 인덱스
|
||||
- 복합 인덱스 (예: `user_id`와 `date` 조합)
|
||||
|
||||
## 6. 함수 및 트리거
|
||||
|
||||
다양한 비즈니스 로직을 지원하기 위해 여러 함수와 트리거가 구현되어 있습니다:
|
||||
|
||||
### 6.1 주요 함수
|
||||
|
||||
- `get_monthly_expenses`: 월별 지출 합계 조회
|
||||
- `get_budget_usage`: 예산 대비 지출 비율 조회
|
||||
- `get_user_cards`: 사용자별 카드 목록 조회
|
||||
- `get_active_limits`: 활성화된 한도 및 사용량 조회
|
||||
- `create_expense_from_template`: 템플릿으로 지출 생성
|
||||
- `mark_notification_as_read`: 알림 읽음 표시
|
||||
- `get_expense_analysis`: 카테고리별 지출 분석
|
||||
- `get_monthly_expense_trend`: 월별 지출 추세 분석
|
||||
|
||||
### 6.2 주요 트리거
|
||||
|
||||
- `update_updated_at_column`: 레코드 업데이트 시 `updated_at` 자동 갱신
|
||||
- `create_user_profile`: Supabase Auth 사용자 생성 시 프로필 자동 생성
|
||||
- `create_limit_warning_notification`: 지출 한도 초과 시 알림 자동 생성
|
||||
- `create_default_analysis_settings`: 사용자 생성 시 기본 분석 설정 자동 생성
|
||||
|
||||
## 7. 마이그레이션 실행 방법
|
||||
|
||||
마이그레이션 파일을 실행하기 위한 스크립트가 제공됩니다:
|
||||
|
||||
```bash
|
||||
# 실행 권한 부여
|
||||
chmod +x run_migrations.sh
|
||||
|
||||
# 스크립트 실행
|
||||
./run_migrations.sh
|
||||
```
|
||||
|
||||
환경 변수를 통해 데이터베이스 연결 정보를 설정할 수 있습니다:
|
||||
|
||||
```bash
|
||||
DB_HOST=localhost DB_PORT=5432 DB_NAME=postgres DB_USER=postgres DB_PASSWORD=postgres ./run_migrations.sh
|
||||
```
|
||||
|
||||
## 8. 향후 확장 계획
|
||||
|
||||
1. **데이터 동기화**: 오프라인 모드 지원을 위한 동기화 메커니즘 구현
|
||||
2. **데이터 분석**: 더 다양한 분석 함수 및 뷰 추가
|
||||
3. **데이터 마이그레이션**: 버전 관리 및 롤백 메커니즘 개선
|
||||
4. **성능 최적화**: 대규모 데이터셋에 대한 쿼리 성능 최적화
|
||||
|
||||
## 9. 참고 사항
|
||||
|
||||
- 모든 날짜/시간 데이터는 타임존 정보를 포함합니다 (`TIMESTAMP WITH TIME ZONE`).
|
||||
- 금액 데이터는 `NUMERIC(12,2)` 타입을 사용하여 정확한 소수점 계산을 보장합니다.
|
||||
- UUID를 기본 키로 사용하여 분산 환경에서의 확장성을 고려했습니다.
|
||||
- 모든 테이블에는 생성 시간(`created_at`)이 자동으로 기록됩니다.
|
||||
292
docs/02_기술_문서/데이터베이스_스키마_스크립트.sql
Normal file
292
docs/02_기술_문서/데이터베이스_스키마_스크립트.sql
Normal file
@@ -0,0 +1,292 @@
|
||||
-- Zellyy Finance 데이터베이스 스키마 스크립트
|
||||
-- 작성일: 2023년
|
||||
|
||||
-- UUID 확장 활성화
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- 타임스탬프 함수 생성 (자동 업데이트용)
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- 1. 사용자 테이블
|
||||
CREATE TABLE users (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
full_name TEXT NOT NULL,
|
||||
profile_image_url TEXT,
|
||||
monthly_income DECIMAL(12, 2),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 사용자 테이블 업데이트 트리거
|
||||
CREATE TRIGGER update_users_updated_at
|
||||
BEFORE UPDATE ON users
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- 사용자 테이블 RLS 정책
|
||||
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "사용자는 자신의 정보만 볼 수 있음" ON users
|
||||
FOR SELECT USING (auth.uid() = id);
|
||||
CREATE POLICY "사용자는 자신의 정보만 수정할 수 있음" ON users
|
||||
FOR UPDATE USING (auth.uid() = id);
|
||||
|
||||
-- 2. 카테고리 테이블
|
||||
CREATE TABLE categories (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
color TEXT NOT NULL,
|
||||
icon TEXT,
|
||||
is_income BOOLEAN DEFAULT FALSE,
|
||||
is_default BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
UNIQUE(user_id, name)
|
||||
);
|
||||
|
||||
-- 카테고리 테이블 업데이트 트리거
|
||||
CREATE TRIGGER update_categories_updated_at
|
||||
BEFORE UPDATE ON categories
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- 카테고리 테이블 RLS 정책
|
||||
ALTER TABLE categories ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "사용자는 자신의 카테고리와 기본 카테고리를 볼 수 있음" ON categories
|
||||
FOR SELECT USING (auth.uid() = user_id OR is_default = TRUE);
|
||||
CREATE POLICY "사용자는 자신의 카테고리만 수정할 수 있음" ON categories
|
||||
FOR UPDATE USING (auth.uid() = user_id AND is_default = FALSE);
|
||||
CREATE POLICY "사용자는 자신의 카테고리만 삭제할 수 있음" ON categories
|
||||
FOR DELETE USING (auth.uid() = user_id AND is_default = FALSE);
|
||||
CREATE POLICY "사용자는 자신의 카테고리만 생성할 수 있음" ON categories
|
||||
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- 3. 카드 테이블
|
||||
CREATE TABLE cards (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
card_number TEXT,
|
||||
card_type TEXT NOT NULL,
|
||||
bank_name TEXT,
|
||||
color TEXT,
|
||||
billing_day INTEGER,
|
||||
payment_day INTEGER,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
UNIQUE(user_id, name)
|
||||
);
|
||||
|
||||
-- 카드 테이블 업데이트 트리거
|
||||
CREATE TRIGGER update_cards_updated_at
|
||||
BEFORE UPDATE ON cards
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- 카드 테이블 RLS 정책
|
||||
ALTER TABLE cards ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "사용자는 자신의 카드만 볼 수 있음" ON cards
|
||||
FOR SELECT USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 카드만 수정할 수 있음" ON cards
|
||||
FOR UPDATE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 카드만 삭제할 수 있음" ON cards
|
||||
FOR DELETE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 카드만 생성할 수 있음" ON cards
|
||||
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- 4. 한도 테이블
|
||||
CREATE TABLE limits (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
card_id UUID REFERENCES cards(id) ON DELETE CASCADE,
|
||||
category_id UUID REFERENCES categories(id) ON DELETE SET NULL,
|
||||
amount DECIMAL(12, 2) NOT NULL,
|
||||
period TEXT NOT NULL, -- 'monthly', 'weekly', 'daily'
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
CONSTRAINT card_or_category_required CHECK (card_id IS NOT NULL OR category_id IS NOT NULL)
|
||||
);
|
||||
|
||||
-- 한도 테이블 업데이트 트리거
|
||||
CREATE TRIGGER update_limits_updated_at
|
||||
BEFORE UPDATE ON limits
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- 한도 테이블 RLS 정책
|
||||
ALTER TABLE limits ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "사용자는 자신의 한도만 볼 수 있음" ON limits
|
||||
FOR SELECT USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 한도만 수정할 수 있음" ON limits
|
||||
FOR UPDATE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 한도만 삭제할 수 있음" ON limits
|
||||
FOR DELETE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 한도만 생성할 수 있음" ON limits
|
||||
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- 5. 지출 테이블
|
||||
CREATE TABLE expenses (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
card_id UUID REFERENCES cards(id) ON DELETE SET NULL,
|
||||
category_id UUID REFERENCES categories(id) ON DELETE SET NULL,
|
||||
amount DECIMAL(12, 2) NOT NULL,
|
||||
description TEXT,
|
||||
date TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
location TEXT,
|
||||
receipt_image_url TEXT,
|
||||
is_income BOOLEAN DEFAULT FALSE,
|
||||
is_recurring BOOLEAN DEFAULT FALSE,
|
||||
recurring_period TEXT, -- 'monthly', 'weekly', 'daily'
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 지출 테이블 업데이트 트리거
|
||||
CREATE TRIGGER update_expenses_updated_at
|
||||
BEFORE UPDATE ON expenses
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- 지출 테이블 RLS 정책
|
||||
ALTER TABLE expenses ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "사용자는 자신의 지출만 볼 수 있음" ON expenses
|
||||
FOR SELECT USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 지출만 수정할 수 있음" ON expenses
|
||||
FOR UPDATE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 지출만 삭제할 수 있음" ON expenses
|
||||
FOR DELETE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 지출만 생성할 수 있음" ON expenses
|
||||
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- 6. 템플릿 테이블
|
||||
CREATE TABLE templates (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
card_id UUID REFERENCES cards(id) ON DELETE SET NULL,
|
||||
category_id UUID REFERENCES categories(id) ON DELETE SET NULL,
|
||||
amount DECIMAL(12, 2) NOT NULL,
|
||||
description TEXT,
|
||||
location TEXT,
|
||||
is_income BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
UNIQUE(user_id, name)
|
||||
);
|
||||
|
||||
-- 템플릿 테이블 업데이트 트리거
|
||||
CREATE TRIGGER update_templates_updated_at
|
||||
BEFORE UPDATE ON templates
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- 템플릿 테이블 RLS 정책
|
||||
ALTER TABLE templates ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "사용자는 자신의 템플릿만 볼 수 있음" ON templates
|
||||
FOR SELECT USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 템플릿만 수정할 수 있음" ON templates
|
||||
FOR UPDATE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 템플릿만 삭제할 수 있음" ON templates
|
||||
FOR DELETE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 템플릿만 생성할 수 있음" ON templates
|
||||
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- 7. 알림 테이블
|
||||
CREATE TABLE notifications (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
type TEXT NOT NULL, -- 'limit_warning', 'payment_due', 'tip', 'system'
|
||||
related_id UUID, -- 관련된 항목의 ID (카드, 한도 등)
|
||||
is_read BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 알림 테이블 업데이트 트리거
|
||||
CREATE TRIGGER update_notifications_updated_at
|
||||
BEFORE UPDATE ON notifications
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- 알림 테이블 RLS 정책
|
||||
ALTER TABLE notifications ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "사용자는 자신의 알림만 볼 수 있음" ON notifications
|
||||
FOR SELECT USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 알림만 수정할 수 있음" ON notifications
|
||||
FOR UPDATE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 알림만 삭제할 수 있음" ON notifications
|
||||
FOR DELETE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "시스템만 알림을 생성할 수 있음" ON notifications
|
||||
FOR INSERT WITH CHECK (auth.uid() = user_id OR auth.uid() IS NULL);
|
||||
|
||||
-- 8. 분석 설정 테이블
|
||||
CREATE TABLE analysis_settings (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
budget_period TEXT DEFAULT 'monthly', -- 'monthly', 'weekly', 'daily'
|
||||
saving_goal DECIMAL(12, 2),
|
||||
analysis_period INTEGER DEFAULT 3, -- 분석에 사용할 과거 데이터 기간 (개월)
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
UNIQUE(user_id)
|
||||
);
|
||||
|
||||
-- 분석 설정 테이블 업데이트 트리거
|
||||
CREATE TRIGGER update_analysis_settings_updated_at
|
||||
BEFORE UPDATE ON analysis_settings
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- 분석 설정 테이블 RLS 정책
|
||||
ALTER TABLE analysis_settings ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "사용자는 자신의 분석 설정만 볼 수 있음" ON analysis_settings
|
||||
FOR SELECT USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 분석 설정만 수정할 수 있음" ON analysis_settings
|
||||
FOR UPDATE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 분석 설정만 삭제할 수 있음" ON analysis_settings
|
||||
FOR DELETE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "사용자는 자신의 분석 설정만 생성할 수 있음" ON analysis_settings
|
||||
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- 기본 카테고리 데이터 삽입
|
||||
INSERT INTO categories (name, color, icon, is_income, is_default) VALUES
|
||||
('식비', '#FF5722', 'restaurant', FALSE, TRUE),
|
||||
('교통', '#2196F3', 'directions_car', FALSE, TRUE),
|
||||
('주거/통신', '#9C27B0', 'home', FALSE, TRUE),
|
||||
('쇼핑', '#4CAF50', 'shopping_bag', FALSE, TRUE),
|
||||
('의료/건강', '#F44336', 'local_hospital', FALSE, TRUE),
|
||||
('문화/여가', '#FFEB3B', 'movie', FALSE, TRUE),
|
||||
('교육', '#795548', 'school', FALSE, TRUE),
|
||||
('경조사/기부', '#607D8B', 'card_giftcard', FALSE, TRUE),
|
||||
('기타', '#9E9E9E', 'more_horiz', FALSE, TRUE),
|
||||
('급여', '#4CAF50', 'payments', TRUE, TRUE),
|
||||
('용돈', '#8BC34A', 'savings', TRUE, TRUE),
|
||||
('상여금', '#CDDC39', 'card_giftcard', TRUE, TRUE),
|
||||
('환불', '#FFC107', 'replay', TRUE, TRUE),
|
||||
('투자수익', '#00BCD4', 'trending_up', TRUE, TRUE),
|
||||
('기타수입', '#9E9E9E', 'more_horiz', TRUE, TRUE);
|
||||
|
||||
-- 인덱스 생성
|
||||
CREATE INDEX idx_expenses_user_id ON expenses(user_id);
|
||||
CREATE INDEX idx_expenses_date ON expenses(date);
|
||||
CREATE INDEX idx_expenses_category_id ON expenses(category_id);
|
||||
CREATE INDEX idx_expenses_card_id ON expenses(card_id);
|
||||
CREATE INDEX idx_limits_user_id ON limits(user_id);
|
||||
CREATE INDEX idx_cards_user_id ON cards(user_id);
|
||||
CREATE INDEX idx_categories_user_id ON categories(user_id);
|
||||
CREATE INDEX idx_notifications_user_id ON notifications(user_id);
|
||||
CREATE INDEX idx_notifications_is_read ON notifications(is_read);
|
||||
87
docs/02_기술_문서/데이터베이스_스키마_요약.md
Normal file
87
docs/02_기술_문서/데이터베이스_스키마_요약.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# 적자 탈출 가계부 - 데이터베이스 스키마 요약
|
||||
|
||||
## 개요
|
||||
이 문서는 적자 탈출 가계부 애플리케이션의 데이터베이스 스키마를 요약합니다. 모든 테이블은 PostgreSQL 기반으로 설계되었으며, Supabase 환경에 최적화되어 있습니다.
|
||||
|
||||
## 기술적 특징
|
||||
- **UUID 기반 기본 키**: 모든 테이블은 UUID 타입의 기본 키를 사용합니다.
|
||||
- **타임존 지원**: 날짜/시간 데이터는 `TIMESTAMP WITH TIME ZONE` 타입을 사용하여 국제적 사용을 지원합니다.
|
||||
- **정확한 금액 계산**: 모든 금액은 `DECIMAL(12, 2)` 타입을 사용하여 정확한 계산을 보장합니다.
|
||||
- **Row Level Security (RLS)**: 모든 테이블에 RLS 정책이 적용되어 사용자는 자신의 데이터만 접근할 수 있습니다.
|
||||
- **자동 생성 시간**: 모든 테이블은 `created_at`과 `updated_at` 필드를 포함하여 데이터 생성 및 수정 시간을 자동으로 기록합니다.
|
||||
- **계층적 카테고리**: 카테고리는 계층 구조를 지원하여 상위/하위 카테고리 관계를 표현할 수 있습니다.
|
||||
- **인덱스 최적화**: 자주 조회되는 필드에 인덱스를 적용하여 성능을 최적화했습니다.
|
||||
|
||||
## 테이블 구조
|
||||
|
||||
### 1. users (01_users.sql)
|
||||
사용자 정보를 관리하는 테이블입니다.
|
||||
- **주요 필드**: id, email, name, created_at, updated_at
|
||||
- **관련 함수**: 사용자 프로필 자동 생성 트리거
|
||||
- **보안**: RLS 정책으로 사용자는 자신의 정보만 접근 가능
|
||||
|
||||
### 2. categories (02_categories.sql)
|
||||
지출/수입 카테고리를 관리하는 테이블입니다.
|
||||
- **주요 필드**: id, user_id, name, parent_id, type, icon, color, is_system
|
||||
- **계층 구조**: parent_id를 통해 상위/하위 카테고리 관계 표현
|
||||
- **기본 데이터**: 15개 이상의 기본 카테고리와 하위 카테고리 제공
|
||||
- **관련 함수**: 카테고리 계층 조회 함수
|
||||
|
||||
### 3. expenses (03_expenses.sql)
|
||||
지출/수입 내역을 관리하는 테이블입니다.
|
||||
- **주요 필드**: id, user_id, amount, is_income, category_id, card_id, date, memo
|
||||
- **관련 함수**: 월별 지출 합계, 카테고리별 지출 분석 함수
|
||||
- **인덱스**: user_id, category_id, date에 인덱스 적용
|
||||
|
||||
### 4. budgets (04_budgets.sql)
|
||||
월별/카테고리별 예산을 관리하는 테이블입니다.
|
||||
- **주요 필드**: id, user_id, category_id, year, month, amount
|
||||
- **관련 함수**: 예산 대비 지출 비율 조회 함수
|
||||
- **인덱스**: user_id, category_id, year, month에 인덱스 적용
|
||||
|
||||
### 5. cards (05_cards.sql)
|
||||
카드 정보를 관리하는 테이블입니다.
|
||||
- **주요 필드**: id, user_id, name, card_number, payment_day, is_active
|
||||
- **관련 함수**: 월별 카드 사용 현황 조회 함수
|
||||
- **보안**: 카드 번호는 마스킹 처리
|
||||
|
||||
### 6. limits (06_limits.sql)
|
||||
지출 한도를 관리하는 테이블입니다.
|
||||
- **주요 필드**: id, user_id, card_id, category_id, amount, period, start_date, end_date
|
||||
- **관련 함수**: 활성화된 한도 및 사용량 조회 함수, 한도 경고 알림 트리거
|
||||
- **제약 조건**: 카드 또는 카테고리 중 하나는 반드시 지정되어야 함
|
||||
|
||||
### 7. templates (07_templates.sql)
|
||||
자주 사용하는 지출/수입 템플릿을 관리하는 테이블입니다.
|
||||
- **주요 필드**: id, user_id, name, amount, is_income, category_id, card_id, usage_count
|
||||
- **관련 함수**: 템플릿 사용 카운터 증가, 템플릿으로 지출/수입 생성, 자주 사용하는 템플릿 조회
|
||||
|
||||
### 8. notifications (08_notifications.sql)
|
||||
알림 시스템을 관리하는 테이블입니다.
|
||||
- **주요 필드**: id, user_id, title, message, type, related_id, is_read, priority
|
||||
- **알림 타입**: limit_warning, budget_warning, payment_reminder, system_message, custom
|
||||
- **관련 함수**: 알림 읽음 표시, 사용자 알림 조회, 예산 초과 알림 생성, 카드 결제일 알림 생성
|
||||
|
||||
### 9. analysis_settings (09_analysis_settings.sql)
|
||||
사용자별 분석 설정을 관리하는 테이블입니다.
|
||||
- **주요 필드**: id, user_id, setting_key, setting_value (JSONB)
|
||||
- **설정 키**: dashboard_widgets, report_preferences, analysis_period, chart_colors 등
|
||||
- **관련 함수**: 분석 설정 가져오기/저장, 기본 분석 설정 생성, 지출 분석 함수(카테고리별 지출 비율, 기간별 지출 추이)
|
||||
|
||||
## 마이그레이션 실행 방법
|
||||
1. `run_migrations.sh` 스크립트를 사용하여 모든 마이그레이션 파일을 순서대로 실행합니다.
|
||||
2. 환경 변수를 통해 데이터베이스 연결 정보를 설정할 수 있습니다.
|
||||
```bash
|
||||
DB_HOST=localhost DB_PORT=5432 DB_NAME=mydb DB_USER=user DB_PASSWORD=pass ./run_migrations.sh
|
||||
```
|
||||
|
||||
## 확장 계획
|
||||
- **데이터 동기화**: 여러 기기 간 데이터 동기화 메커니즘 구현
|
||||
- **분석 기능 확장**: 고급 재무 분석 및 예측 기능 추가
|
||||
- **성능 최적화**: 대량의 트랜잭션 데이터 처리를 위한 파티셔닝 및 인덱싱 최적화
|
||||
- **API 엔드포인트**: RESTful API 및 GraphQL 엔드포인트 개발
|
||||
|
||||
## 참고 사항
|
||||
- 모든 SQL 파일은 `migrations` 디렉토리에 순서대로 번호가 매겨져 있습니다.
|
||||
- 각 테이블은 자체 SQL 파일에 정의되어 있어 관리가 용이합니다.
|
||||
- 트리거와 함수는 관련 테이블과 동일한 파일에 정의되어 있습니다.
|
||||
68
docs/02_기술_문서/마이그레이션_관리.md
Normal file
68
docs/02_기술_문서/마이그레이션_관리.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# 마이그레이션 파일 관리
|
||||
|
||||
## 개요
|
||||
Zellyy Finance 프로젝트의 데이터베이스 마이그레이션 파일은 Gitea 저장소에서 단일 소스로 관리됩니다. 이 문서는 마이그레이션 파일의 관리 방법과 위치에 대한 정보를 제공합니다.
|
||||
|
||||
## 마이그레이션 파일 위치
|
||||
- **소스 코드 저장소**: `/Users/hansoo./Dev/Zellyy_Finance/backend/migrations/`
|
||||
- **Git 저장소 URL**: [Zellyy_Finance Git Repository](https://gitea.com/hansoo/Zellyy_Finance)
|
||||
- **옵시디언 참조 디렉토리**: `/1. Project/ZELLYY/적자 탈출 가계부/02_기술_문서/migrations/`
|
||||
(참고: 이 디렉토리는 실제 SQL 파일을 포함하지 않으며, 참조용 디렉토리입니다.)
|
||||
|
||||
## 코드 관리 원칙
|
||||
- **단일 소스 원칙**: 모든 코드는 Gitea에서만 관리합니다.
|
||||
- **옵시디언 문서화 원칙**: 옵시디언에는 문서만 저장하고 코드는 저장하지 않습니다.
|
||||
- **참조 링크 사용**: 옵시디언에서는 Gitea의 코드를 참조하는 링크를 사용합니다.
|
||||
|
||||
> 참고: 코드 관련 기술 문서도 Gitea에서만 관리합니다. 자세한 내용은 [기술문서_관리](/1.%20Project/ZELLYY/적자%20탈출%20가계부/02_기술_문서/기술문서_관리.md) 문서를 참조하세요.
|
||||
|
||||
## 마이그레이션 파일 목록
|
||||
마이그레이션 파일은 다음과 같은 순서로 실행됩니다:
|
||||
|
||||
1. `01_users.sql`: 사용자 테이블 및 인증 관련 기능
|
||||
2. `02_categories.sql`: 카테고리 테이블 및 계층 구조
|
||||
3. `03_expenses.sql`: 지출/수입 내역 테이블
|
||||
4. `04_budgets.sql`: 예산 관리 테이블
|
||||
5. `05_cards.sql`: 카드 정보 관리 테이블
|
||||
6. `06_limits.sql`: 지출 한도 관리 테이블
|
||||
7. `07_templates.sql`: 자주 사용하는 지출/수입 템플릿
|
||||
8. `08_notifications.sql`: 알림 시스템 테이블
|
||||
9. `09_analysis_settings.sql`: 사용자별 분석 설정 테이블
|
||||
|
||||
## 마이그레이션 실행 방법
|
||||
마이그레이션은 `run_migrations.sh` 스크립트를 통해 실행됩니다:
|
||||
```bash
|
||||
cd /Users/hansoo./Dev/Zellyy_Finance/backend
|
||||
./run_migrations.sh
|
||||
```
|
||||
|
||||
## 변경 관리
|
||||
마이그레이션 파일을 변경해야 할 경우, 다음 절차를 따릅니다:
|
||||
|
||||
1. Gitea 저장소의 파일만 수정합니다.
|
||||
2. 변경 사항을 커밋하고 푸시합니다.
|
||||
3. 이 문서에 중요한 변경 사항을 기록합니다.
|
||||
|
||||
## 주의사항
|
||||
- 옵시디언에는 코드를 저장하지 않는 원칙에 따라, 원래 있던 SQL 파일들은 삭제되었습니다.
|
||||
- 코드는 Gitea 저장소에서만 관리되므로, 코드 작업 시에는 항상 Gitea 저장소를 참조하세요.
|
||||
- 옵시디언의 한글 디렉토리명으로 인해 일부 명령어에서 경로 문제가 발생할 수 있습니다. 스크립트나 명령어에서 경로를 사용할 때는 Gitea의 영문 경로를 사용하세요.
|
||||
|
||||
## 디렉토리 구조 관련 참고사항
|
||||
- **옵시디언 디렉토리 구조**:
|
||||
- `/1. Project/ZELLYY/적자 탈출 가계부/02_기술_문서/migrations/`: 참조용 빈 디렉토리
|
||||
- `/1. Project/ZELLYY/적자 탈출 가계부/02_기술_문서/마이그레이션_관리.md`: 이 문서
|
||||
|
||||
- **Gitea 디렉토리 구조**:
|
||||
- `/Users/hansoo./Dev/Zellyy_Finance/backend/migrations/`: 실제 SQL 파일 포함
|
||||
- `/Users/hansoo./Dev/Zellyy_Finance/backend/run_migrations.sh`: 마이그레이션 실행 스크립트
|
||||
|
||||
한글 디렉토리명을 사용하는 옵시디언 경로는 일부 도구나 명령어에서 문제가 발생할 수 있으므로, 코드 작업 시에는 항상 Gitea의 영문 경로를 사용하는 것이 좋습니다.
|
||||
|
||||
## 최근 업데이트
|
||||
- 2025-02-27: 옵시디언과 Gitea의 마이그레이션 파일을 통합하여 Gitea를 단일 소스로 지정
|
||||
- 2025-02-27: 디렉토리 구조 관련 주의사항 추가
|
||||
- 2025-02-27: 옵시디언의 SQL 파일 삭제 및 백업 정보 추가
|
||||
- 2025-02-27: 문서 통합 (migrations/README.md 삭제 및 내용 통합)
|
||||
- 2025-02-27: migrations_backup 디렉토리 삭제
|
||||
- 2025-02-27: 옵시디언의 run_migrations.sh 파일 삭제 (Gitea에서만 관리)
|
||||
265
docs/02_기술_문서/보안_전략_및_구현.md
Normal file
265
docs/02_기술_문서/보안_전략_및_구현.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# 적자 탈출 가계부 - 보안 전략 및 구현
|
||||
|
||||
## 1. 개요
|
||||
|
||||
이 문서는 적자 탈출 가계부 애플리케이션의 보안 전략과 구현 방법을 정의합니다. 사용자의 재정 데이터를 다루는 앱으로서, 데이터 보안과 개인정보 보호는 최우선 과제입니다.
|
||||
|
||||
## 2. 보안 원칙
|
||||
|
||||
적자 탈출 가계부는 다음과 같은 보안 원칙을 따릅니다:
|
||||
|
||||
1. **최소 권한 원칙**: 각 구성 요소는 필요한 최소한의 권한만 가집니다.
|
||||
2. **심층 방어**: 여러 계층의 보안 통제를 구현하여 단일 방어선 실패에 대비합니다.
|
||||
3. **기본적 보안**: 모든 시스템은 기본적으로 안전한 설정으로 구성됩니다.
|
||||
4. **개인정보 보호 중심 설계**: 개인정보 보호는 설계 단계부터 고려됩니다.
|
||||
5. **투명성**: 사용자에게 데이터 수집 및 사용 방식을 명확히 알립니다.
|
||||
|
||||
## 3. 데이터 보안
|
||||
|
||||
### 3.1 저장 데이터(Data at Rest) 보안
|
||||
|
||||
#### 로컬 저장소 보안
|
||||
- **암호화**: 모든 민감한 로컬 데이터는 AES-256 암호화를 사용하여 저장됩니다.
|
||||
- **키 관리**: 암호화 키는 안전한 키체인/키스토어에 저장됩니다.
|
||||
- **민감 정보 처리**: 카드 번호와 같은 민감한 정보는 부분적으로만 저장하고 마스킹 처리합니다.
|
||||
|
||||
#### 서버 측 데이터 보안
|
||||
- **데이터베이스 암호화**: Supabase의 RLS(Row Level Security)를 활용한 데이터 접근 제어
|
||||
- **백업 암호화**: 모든 데이터베이스 백업은 암호화되어 저장됩니다.
|
||||
- **데이터 분리**: 사용자 인증 정보와 트랜잭션 데이터는 별도의 테이블에 저장됩니다.
|
||||
|
||||
### 3.2 전송 데이터(Data in Transit) 보안
|
||||
|
||||
- **TLS/SSL**: 모든 API 통신은 TLS 1.3을 사용하여 암호화됩니다.
|
||||
- **인증서 핀닝**: 모바일 앱에서는 인증서 핀닝을 구현하여 중간자 공격을 방지합니다.
|
||||
- **안전한 API 설계**: 모든 API 엔드포인트는 인증 및 권한 검사를 수행합니다.
|
||||
|
||||
### 3.3 데이터 접근 제어
|
||||
|
||||
- **사용자 기반 접근 제어**: 각 사용자는 자신의 데이터만 접근할 수 있습니다.
|
||||
- **역할 기반 접근 제어**: 관리자, 지원팀 등 역할에 따라 접근 권한이 다르게 설정됩니다.
|
||||
- **API 접근 제한**: API 키는 필요한 최소한의 권한만 부여받습니다.
|
||||
|
||||
## 4. 사용자 인증 및 권한 부여
|
||||
|
||||
### 4.1 인증 메커니즘
|
||||
|
||||
- **강력한 비밀번호 정책**: 최소 8자, 대소문자, 숫자, 특수문자 포함 필요
|
||||
- **다중 인증(MFA)**: 선택적 2단계 인증 지원 (이메일, SMS, 인증 앱)
|
||||
- **소셜 로그인**: OAuth 2.0 기반의 안전한 소셜 로그인 통합
|
||||
- **세션 관리**: 안전한 세션 생성, 만료 및 갱신 메커니즘
|
||||
|
||||
### 4.2 권한 부여
|
||||
|
||||
- **JWT 기반 인증**: 서명된 JWT 토큰을 사용한 API 접근 제어
|
||||
- **토큰 만료**: 액세스 토큰은 짧은 수명(15분)을 가지며, 리프레시 토큰으로 갱신
|
||||
- **권한 검증**: 모든 API 요청은 권한 검증 미들웨어를 통과해야 함
|
||||
|
||||
### 4.3 계정 보안
|
||||
|
||||
- **계정 잠금**: 연속된 로그인 실패 시 일시적 계정 잠금
|
||||
- **비밀번호 재설정**: 안전한 비밀번호 재설정 프로세스 (시간 제한 토큰 사용)
|
||||
- **로그인 알림**: 새로운 기기에서 로그인 시 사용자에게 알림
|
||||
- **세션 관리**: 활성 세션 목록 확인 및 원격 로그아웃 기능
|
||||
|
||||
## 5. 애플리케이션 보안
|
||||
|
||||
### 5.1 모바일 앱 보안
|
||||
|
||||
- **코드 난독화**: 앱 코드 난독화를 통한 리버스 엔지니어링 방지
|
||||
- **루팅/탈옥 감지**: 루팅/탈옥된 기기 감지 및 보안 경고
|
||||
- **스크린샷 방지**: 민감한 화면에서 스크린샷 방지 기능
|
||||
- **자동 잠금**: 일정 시간 미사용 시 앱 자동 잠금
|
||||
- **메모리 보안**: 민감한 데이터는 사용 후 메모리에서 즉시 제거
|
||||
|
||||
### 5.2 API 보안
|
||||
|
||||
- **입력 검증**: 모든 사용자 입력은 서버와 클라이언트 양쪽에서 검증
|
||||
- **출력 인코딩**: 모든 출력 데이터는 적절히 인코딩하여 XSS 방지
|
||||
- **CSRF 방지**: 상태 변경 작업에 CSRF 토큰 사용
|
||||
- **속도 제한**: API 요청에 속도 제한 적용하여 DoS 공격 방지
|
||||
- **보안 헤더**: 적절한 보안 HTTP 헤더 사용 (HSTS, CSP, X-Content-Type-Options 등)
|
||||
|
||||
### 5.3 서버 보안
|
||||
|
||||
- **최신 패치**: 모든 서버 소프트웨어는 최신 보안 패치 유지
|
||||
- **방화벽 구성**: 필요한 포트만 개방하는 엄격한 방화벽 정책
|
||||
- **침입 탐지**: 비정상적인 접근 패턴 모니터링 및 알림
|
||||
- **로그 관리**: 보안 관련 로그 중앙 집중화 및 분석
|
||||
- **취약점 스캔**: 정기적인 취약점 스캔 및 패치
|
||||
|
||||
## 6. 개인정보 보호
|
||||
|
||||
### 6.1 데이터 최소화
|
||||
|
||||
- **필수 정보만 수집**: 서비스 제공에 필요한 최소한의 개인정보만 수집
|
||||
- **데이터 보존 정책**: 목적 달성 후 불필요한 데이터 자동 삭제
|
||||
- **익명화/가명화**: 가능한 경우 데이터 익명화 또는 가명화 처리
|
||||
|
||||
### 6.2 사용자 통제권
|
||||
|
||||
- **개인정보 접근**: 사용자가 자신의 모든 데이터에 접근할 수 있는 기능
|
||||
- **데이터 내보내기**: 표준 형식으로 데이터 내보내기 기능
|
||||
- **계정 삭제**: 완전한 계정 및 데이터 삭제 기능
|
||||
- **동의 관리**: 명확한 동의 획득 및 철회 프로세스
|
||||
|
||||
### 6.3 개인정보 처리방침
|
||||
|
||||
- **명확한 설명**: 이해하기 쉬운 언어로 데이터 수집 및 사용 방식 설명
|
||||
- **제3자 공유**: 데이터가 공유되는 제3자 명시
|
||||
- **연락처 정보**: 개인정보 관련 문의를 위한 연락처 제공
|
||||
- **정기 업데이트**: 정책 변경 시 사용자에게 알림
|
||||
|
||||
## 7. 보안 모니터링 및 대응
|
||||
|
||||
### 7.1 모니터링 시스템
|
||||
|
||||
- **로그 모니터링**: 보안 관련 로그 실시간 모니터링
|
||||
- **이상 탐지**: 비정상적인 사용자 행동 패턴 감지
|
||||
- **성능 모니터링**: 서비스 가용성 및 성능 모니터링
|
||||
- **알림 시스템**: 보안 이벤트 발생 시 즉시 알림
|
||||
|
||||
### 7.2 인시던트 대응
|
||||
|
||||
- **대응 계획**: 보안 사고 유형별 대응 절차 문서화
|
||||
- **에스컬레이션 프로세스**: 심각도에 따른 에스컬레이션 경로 정의
|
||||
- **복구 절차**: 데이터 및 서비스 복구 절차 정의
|
||||
- **사후 분석**: 사고 원인 분석 및 재발 방지 대책 수립
|
||||
|
||||
### 7.3 정기적인 보안 평가
|
||||
|
||||
- **취약점 스캔**: 월간 자동화된 취약점 스캔
|
||||
- **침투 테스트**: 연간 전문가에 의한 침투 테스트
|
||||
- **코드 리뷰**: 보안 중심의 코드 리뷰 프로세스
|
||||
- **위험 평가**: 분기별 보안 위험 평가 및 대응 계획 업데이트
|
||||
|
||||
## 8. 규정 준수
|
||||
|
||||
### 8.1 관련 법규 준수
|
||||
|
||||
- **개인정보 보호법**: 국내 개인정보 보호법 준수
|
||||
- **GDPR**: 유럽 사용자를 위한 GDPR 요구사항 준수
|
||||
- **CCPA**: 캘리포니아 소비자 개인정보 보호법 준수
|
||||
- **금융 관련 규정**: 금융 데이터 처리 관련 규정 준수
|
||||
|
||||
### 8.2 내부 정책
|
||||
|
||||
- **보안 정책**: 직원 및 개발자를 위한 보안 정책 문서화
|
||||
- **접근 제어 정책**: 내부 시스템 및 데이터 접근 정책
|
||||
- **인시던트 대응 정책**: 보안 사고 대응 정책 및 절차
|
||||
- **변경 관리 정책**: 시스템 변경에 대한 보안 검토 절차
|
||||
|
||||
## 9. 구현 세부 사항
|
||||
|
||||
### 9.1 사용자 인증 구현
|
||||
|
||||
```typescript
|
||||
// JWT 토큰 생성 함수
|
||||
function generateJWT(userId: string): string {
|
||||
const payload = {
|
||||
sub: userId,
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + (15 * 60), // 15분 유효
|
||||
role: 'user'
|
||||
};
|
||||
|
||||
return jwt.sign(payload, process.env.JWT_SECRET, { algorithm: 'HS256' });
|
||||
}
|
||||
|
||||
// 비밀번호 해싱 함수
|
||||
async function hashPassword(password: string): Promise<string> {
|
||||
const salt = await bcrypt.genSalt(12);
|
||||
return bcrypt.hash(password, salt);
|
||||
}
|
||||
|
||||
// 비밀번호 검증 함수
|
||||
async function verifyPassword(password: string, hashedPassword: string): Promise<boolean> {
|
||||
return bcrypt.compare(password, hashedPassword);
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 데이터 암호화 구현
|
||||
|
||||
```typescript
|
||||
// 데이터 암호화 함수
|
||||
function encryptData(data: string, key: Buffer): string {
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
||||
|
||||
let encrypted = cipher.update(data, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
|
||||
const authTag = cipher.getAuthTag();
|
||||
|
||||
// IV와 인증 태그를 암호문과 함께 저장
|
||||
return iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted;
|
||||
}
|
||||
|
||||
// 데이터 복호화 함수
|
||||
function decryptData(encryptedData: string, key: Buffer): string {
|
||||
const parts = encryptedData.split(':');
|
||||
const iv = Buffer.from(parts[0], 'hex');
|
||||
const authTag = Buffer.from(parts[1], 'hex');
|
||||
const encryptedText = parts[2];
|
||||
|
||||
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
```
|
||||
|
||||
### 9.3 API 보안 구현
|
||||
|
||||
```typescript
|
||||
// API 요청 인증 미들웨어
|
||||
function authMiddleware(req, res, next) {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: '인증 토큰이 필요합니다' });
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(401).json({ error: '유효하지 않은 토큰입니다' });
|
||||
}
|
||||
}
|
||||
|
||||
// API 속도 제한 미들웨어
|
||||
const rateLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15분
|
||||
max: 100, // IP당 최대 요청 수
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { error: '너무 많은 요청을 보냈습니다. 잠시 후 다시 시도해주세요.' }
|
||||
});
|
||||
```
|
||||
|
||||
## 10. 보안 테스트
|
||||
|
||||
### 10.1 자동화된 보안 테스트
|
||||
|
||||
- **정적 코드 분석**: SonarQube를 사용한 코드 취약점 분석
|
||||
- **의존성 검사**: npm audit을 통한 취약한 의존성 검사
|
||||
- **API 보안 테스트**: OWASP ZAP을 사용한 자동화된 API 보안 테스트
|
||||
- **모의 침투 테스트**: 정기적인 모의 침투 테스트 수행
|
||||
|
||||
### 10.2 수동 보안 검토
|
||||
|
||||
- **코드 리뷰**: 보안 중심의 코드 리뷰 체크리스트
|
||||
- **아키텍처 검토**: 정기적인 보안 아키텍처 검토
|
||||
- **구성 검토**: 서버 및 클라우드 구성 보안 검토
|
||||
- **권한 검토**: 사용자 권한 및 접근 제어 검토
|
||||
|
||||
## 11. 결론
|
||||
|
||||
적자 탈출 가계부의 보안 전략은 사용자 데이터 보호를 최우선으로 합니다. 이 문서에 정의된 보안 조치들은 지속적으로 평가되고 개선되어야 합니다. 보안은 일회성 작업이 아닌 지속적인 프로세스임을 인식하고, 새로운 위협과 취약점에 대응하기 위해 보안 전략을 정기적으로 업데이트할 것입니다.
|
||||
Reference in New Issue
Block a user