Files

153 lines
5.9 KiB
Python

import os
import datetime
from flask import current_app
from ..models.memo_repo import MemoRepository
from ..utils import extract_links, parse_and_clean_metadata, generate_auto_title
from ..security import encrypt_content, decrypt_content
from ..constants import GROUP_DEFAULT
class MemoService:
@staticmethod
def get_all_memos(filters, limit=20, offset=0):
return MemoRepository.get_all(filters, limit, offset)
@staticmethod
def get_memo_by_id(memo_id):
return MemoRepository.get_by_id(memo_id)
@staticmethod
def create_memo(data):
content = data.get('content', '').strip()
group_name = data.get('group_name', GROUP_DEFAULT).strip()
user_tags = data.get('tags', [])
is_encrypted = 1 if data.get('is_encrypted') else 0
password = data.get('password', '').strip()
# 1. 메타데이터 파싱 및 본문 정리
new_content, final_group, final_tags = parse_and_clean_metadata(content, ui_group=group_name, ui_tags=user_tags)
content = new_content
group_name = final_group
user_tags = final_tags
# 2. 제목 자동 생성
title = data.get('title', '').strip()
if not title:
title = generate_auto_title(content)
# 3. 암호화 처리
if is_encrypted and password:
content = encrypt_content(content, password)
elif is_encrypted and not password:
raise ValueError('Password required for encryption')
now = datetime.datetime.now().isoformat()
repo_data = {
'title': title,
'content': content,
'color': data.get('color', '#2c3e50'),
'is_pinned': 1 if data.get('is_pinned') else 0,
'status': data.get('status', 'active').strip(),
'group_name': group_name,
'category': data.get('category'),
'is_encrypted': is_encrypted,
'created_at': now,
'updated_at': now
}
links = extract_links(content)
attachment_filenames = data.get('attachment_filenames', [])
return MemoRepository.create(repo_data, tags=user_tags, links=links, attachment_filenames=attachment_filenames)
@staticmethod
def update_memo(memo_id, data):
password = data.get('password', '').strip()
# 1. 기존 메모 정보 조회 (암호화 검증용)
memo = MemoRepository.get_by_id(memo_id)
if not memo:
return None, "Memo not found"
if memo['is_encrypted']:
if not password:
return None, "msg_encrypted_locked"
if decrypt_content(memo['content'], password) is None:
return None, "msg_auth_failed"
# 2. 데이터 가공
content = data.get('content')
group_name = data.get('group_name')
user_tags = data.get('tags')
if content is not None:
new_content, final_group, final_tags = parse_and_clean_metadata(
content,
ui_group=(group_name or memo['group_name']),
ui_tags=(user_tags if user_tags is not None else [])
)
content = new_content
group_name = final_group
user_tags = final_tags
title = data.get('title')
if title == "": # 빈 문자열일 때만 자동 생성
title = generate_auto_title(content or "")
now = datetime.datetime.now().isoformat()
updates = {'updated_at': now}
# 암호화 처리
is_encrypted = data.get('is_encrypted')
final_content = content.strip() if content is not None else None
# 기존 암호화 상태 유지 혹은 신규 설정 시 암호화 적용
if (is_encrypted or (is_encrypted is None and memo['is_encrypted'])) and password:
if final_content is not None:
final_content = encrypt_content(final_content, password)
if title is not None: updates['title'] = title.strip()
if final_content is not None: updates['content'] = final_content
if data.get('color') is not None: updates['color'] = data.get('color')
if data.get('is_pinned') is not None: updates['is_pinned'] = 1 if data.get('is_pinned') else 0
if data.get('status') is not None: updates['status'] = data.get('status').strip()
if group_name is not None: updates['group_name'] = group_name.strip()
if is_encrypted is not None: updates['is_encrypted'] = 1 if is_encrypted else 0
if data.get('category') is not None: updates['category'] = data.get('category')
links = extract_links(content) if content is not None else None
attachment_filenames = data.get('attachment_filenames')
MemoRepository.update(memo_id, updates, tags=user_tags, links=links, attachment_filenames=attachment_filenames)
return True, "Updated"
@staticmethod
def delete_memo(memo_id):
memo = MemoRepository.get_by_id(memo_id)
if not memo: return False, "Memo not found"
if memo['is_encrypted']: return False, "msg_encrypted_locked"
# 물리 파일 삭제
upload_folder = current_app.config['UPLOAD_FOLDER']
for f in memo['attachments']:
filepath = os.path.join(upload_folder, f['filename'])
if os.path.exists(filepath):
os.remove(filepath)
MemoRepository.delete(memo_id)
return True, "Deleted"
@staticmethod
def decrypt_memo(memo_id, password):
memo = MemoRepository.get_by_id(memo_id)
if not memo: return None, "Memo not found"
if not memo['is_encrypted']: return memo['content'], None
decrypted = decrypt_content(memo['content'], password)
if decrypted is None: return None, "Invalid password"
return decrypted, None
@staticmethod
def get_heatmap_stats(days=365):
return MemoRepository.get_heatmap(days)