diff --git a/app/models/journal.py b/app/models/journal.py index 6db0e58..c903c5d 100644 --- a/app/models/journal.py +++ b/app/models/journal.py @@ -43,6 +43,9 @@ class JournalCategory(Base): name: Mapped[str] = mapped_column(String(50), nullable=False) color: Mapped[str | None] = mapped_column(String(20), nullable=True) sort_order: Mapped[int | None] = mapped_column(Integer, nullable=True) + # 이 카테고리로 새 기록을 쓸 때 내용칸에 미리 채워주는 틀(예: Story/Feelings/Decisions/... 회고 양식). + # 사용자가 직접 타이핑을 시작하면 더 이상 덮어쓰지 않는다(프론트에서 처리). + content_template: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(server_default=func.now(), nullable=False) entries: Mapped[list["JournalEntry"]] = relationship( diff --git a/app/routers/journal_pages.py b/app/routers/journal_pages.py index fa90fd4..c4e97ea 100644 --- a/app/routers/journal_pages.py +++ b/app/routers/journal_pages.py @@ -30,6 +30,9 @@ JOURNAL_MOOD_OPTIONS = [ ] templates.env.globals["journal_mood_options"] = JOURNAL_MOOD_OPTIONS templates.env.globals["journal_mood_emoji"] = {m.value: emoji for m, emoji, _ in JOURNAL_MOOD_OPTIONS} +# Jinja2 내장 |tojson 필터가 이 policy를 읽어서 json.dumps에 넘긴다 — 기본값(ensure_ascii=True)이면 +# 한글이 \uXXXX로 이스케이프돼 응답 본문에서 읽기 힘들어진다. +templates.env.policies["json.dumps_kwargs"] = {"ensure_ascii": False} def _parse_moods(raw: str) -> list[JournalMood]: @@ -111,6 +114,7 @@ def journal_page( "tab": tab, "categories": categories, "category_id": category_id, + "category_templates": {str(c.id): c.content_template or "" for c in categories}, "entry_counts": journal_service.count_entries_by_category(db, current.id) if tab == "manage" else {}, "on_this_day": journal_service.get_on_this_day(db, current.id, today), "prompt": journal_service.get_random_prompt(db), @@ -274,14 +278,18 @@ def delete_attachment_page( @router.post("/journal/categories/new") def create_category_page( - request: Request, name: str = Form(""), color: str | None = Form(None), db: Session = Depends(get_db) + request: Request, + name: str = Form(""), + color: str | None = Form(None), + content_template: str | None = Form(None), + db: Session = Depends(get_db), ): current = _current_user_or_redirect(request, db) if isinstance(current, RedirectResponse): return current try: - data = JournalCategoryCreate(name=name, color=color) + data = JournalCategoryCreate(name=name, color=color, content_template=content_template) except ValidationError as exc: message = exc.errors()[0]["msg"].removeprefix("Value error, ") return HTMLResponse(message) @@ -303,6 +311,7 @@ def edit_category_page( category_id: int, name: str = Form(""), color: str | None = Form(None), + content_template: str | None = Form(None), db: Session = Depends(get_db), ): current = _current_user_or_redirect(request, db) @@ -314,7 +323,7 @@ def edit_category_page( return Response(status_code=404) try: - data = JournalCategoryCreate(name=name, color=color) + data = JournalCategoryCreate(name=name, color=color, content_template=content_template) except ValidationError as exc: message = exc.errors()[0]["msg"].removeprefix("Value error, ") return HTMLResponse(message) diff --git a/app/schemas/journal.py b/app/schemas/journal.py index d569093..d932536 100644 --- a/app/schemas/journal.py +++ b/app/schemas/journal.py @@ -8,6 +8,7 @@ from app.models.journal import JournalAttachmentType, JournalMood class JournalCategoryBase(BaseModel): name: str color: str | None = None + content_template: str | None = None @field_validator("name") @classmethod @@ -25,6 +26,14 @@ class JournalCategoryBase(BaseModel): v = v.strip() return v or None + @field_validator("content_template") + @classmethod + def blank_template_to_none(cls, v: str | None) -> str | None: + if v is None: + return None + v = v.strip() + return v or None + class JournalCategoryCreate(JournalCategoryBase): pass @@ -36,6 +45,7 @@ class JournalCategoryOut(BaseModel): id: int name: str color: str | None + content_template: str | None sort_order: int | None created_at: datetime diff --git a/app/services/journal_service.py b/app/services/journal_service.py index 7a885f2..816cfaa 100644 --- a/app/services/journal_service.py +++ b/app/services/journal_service.py @@ -34,6 +34,27 @@ ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/webp", "image/gif"} ALLOWED_VIDEO_TYPES = {"video/mp4", "video/quicktime"} THUMBNAIL_WIDTH = 400 DEFAULT_CATEGORY_NAME = "일상" +DEFAULT_CATEGORY_TEMPLATE = """**1. Story : 오늘 무슨 일이 있었나요?** + +- + +**2. Feelings : 오늘 들었던 생각과 나의 감정은?** + +- + +**3. Decisions : 오늘 내가 내린 결정이 있나요?** + +- + +**4. Insights : 나에 대해 새롭게 알게된 사실이 있나요?** + +- + +→ 나에 대한 인사이트는 메타인지를 키워주는 **소중한 기록**입니다. 꼭 보관하세요. + +**5. Actions : 내가 다음에 할 행동은 무엇인가요?** + +- """ # ---- 카테고리 ---- @@ -54,7 +75,9 @@ def get_category(db: Session, category_id: int, user_id: int) -> JournalCategory def create_category(db: Session, user_id: int, data: JournalCategoryCreate) -> JournalCategory: - category = JournalCategory(user_id=user_id, name=data.name, color=data.color) + category = JournalCategory( + user_id=user_id, name=data.name, color=data.color, content_template=data.content_template + ) db.add(category) db.commit() db.refresh(category) @@ -64,6 +87,7 @@ def create_category(db: Session, user_id: int, data: JournalCategoryCreate) -> J def update_category(db: Session, category: JournalCategory, data: JournalCategoryCreate) -> JournalCategory: category.name = data.name category.color = data.color + category.content_template = data.content_template db.commit() db.refresh(category) return category @@ -79,7 +103,9 @@ def ensure_default_category(db: Session, user_id: int) -> JournalCategory: ) if existing is not None: return existing - return create_category(db, user_id, JournalCategoryCreate(name=DEFAULT_CATEGORY_NAME)) + return create_category( + db, user_id, JournalCategoryCreate(name=DEFAULT_CATEGORY_NAME, content_template=DEFAULT_CATEGORY_TEMPLATE) + ) def reorder_categories(db: Session, user_id: int, ordered_ids: list[int]) -> None: diff --git a/app/templates/journal.html b/app/templates/journal.html index a9a43b9..74876c7 100644 --- a/app/templates/journal.html +++ b/app/templates/journal.html @@ -59,6 +59,10 @@ +
+ + +
@@ -85,6 +89,10 @@
+
+ + +
@@ -113,6 +121,17 @@ open: false, moods: [], toggleMood(v) { this.moods.includes(v) ? this.moods = this.moods.filter(m => m !== v) : this.moods.push(v) }, + categoryTemplates: {{ category_templates|tojson|forceescape }}, + content: '', + lastAutoFilled: '', + onCategoryChange(categoryId) { + const tmpl = this.categoryTemplates[categoryId] || ''; + if (this.content === this.lastAutoFilled) { + this.content = tmpl; + this.lastAutoFilled = tmpl; + } + }, + init() { this.onCategoryChange(this.$refs.categorySelect.value); }, }" >