add journaling feature: categories, tags, attachments, calendar, and multi-select emotions
Adds an 8th development stage that lets users keep a free-form journal alongside habit tracking, reusing the existing Google OAuth/DB/PWA infrastructure instead of a separate project. Users organize entries into custom categories, filter by a month calendar with day-detail drill-down, attach photos/videos (served via an authenticated route, never /static), tag entries, and pick multiple emotions per entry from a curated 9-option set. Includes a global journal prompt bank for lightweight guided journaling. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
from datetime import date
|
||||
from io import BytesIO
|
||||
|
||||
from PIL import Image
|
||||
from starlette.datastructures import Headers, UploadFile
|
||||
|
||||
from app.schemas.journal import JournalCategoryCreate, JournalEntryCreate
|
||||
from app.services import journal_service
|
||||
|
||||
|
||||
def _make_entry_with_attachment(db_session, user_id, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(journal_service.settings, "journal_media_root", str(tmp_path))
|
||||
category = journal_service.create_category(db_session, user_id, JournalCategoryCreate(name="일상"))
|
||||
entry = journal_service.create_entry(
|
||||
db_session,
|
||||
user_id,
|
||||
JournalEntryCreate(category_id=category.id, entry_date=date.today(), content="기록"),
|
||||
)
|
||||
buf = BytesIO()
|
||||
Image.new("RGB", (800, 600), "blue").save(buf, format="PNG")
|
||||
upload = UploadFile(file=BytesIO(buf.getvalue()), filename="photo.png", headers=Headers({"content-type": "image/png"}))
|
||||
attachment = journal_service.save_attachment(db_session, entry, upload)
|
||||
return attachment
|
||||
|
||||
|
||||
def test_get_media_requires_login(client):
|
||||
response = client.get("/api/journal/media/1")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_get_media_returns_file_for_owner(auth_client, db_session, test_user, tmp_path, monkeypatch):
|
||||
attachment = _make_entry_with_attachment(db_session, test_user.id, tmp_path, monkeypatch)
|
||||
|
||||
response = auth_client.get(f"/api/journal/media/{attachment.id}")
|
||||
assert response.status_code == 200
|
||||
assert response.content[:8] == b"\x89PNG\r\n\x1a\n"
|
||||
|
||||
|
||||
def test_get_media_thumbnail_variant(auth_client, db_session, test_user, tmp_path, monkeypatch):
|
||||
attachment = _make_entry_with_attachment(db_session, test_user.id, tmp_path, monkeypatch)
|
||||
|
||||
response = auth_client.get(f"/api/journal/media/{attachment.id}?thumbnail=true")
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_get_media_returns_404_for_missing_attachment(auth_client):
|
||||
response = auth_client.get("/api/journal/media/9999")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_get_media_returns_404_for_other_users_attachment(auth_client, db_session, other_user, tmp_path, monkeypatch):
|
||||
attachment = _make_entry_with_attachment(db_session, other_user.id, tmp_path, monkeypatch)
|
||||
|
||||
response = auth_client.get(f"/api/journal/media/{attachment.id}")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_reorder_categories_requires_login(client):
|
||||
response = client.post("/api/journal/categories/reorder", json={"category_ids": [1, 2]})
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_reorder_categories_updates_sort_order(auth_client, db_session, test_user):
|
||||
a = journal_service.create_category(db_session, test_user.id, JournalCategoryCreate(name="A"))
|
||||
b = journal_service.create_category(db_session, test_user.id, JournalCategoryCreate(name="B"))
|
||||
|
||||
response = auth_client.post("/api/journal/categories/reorder", json={"category_ids": [b.id, a.id]})
|
||||
assert response.status_code == 200
|
||||
|
||||
db_session.refresh(a)
|
||||
db_session.refresh(b)
|
||||
assert b.sort_order == 0
|
||||
assert a.sort_order == 1
|
||||
|
||||
|
||||
def test_reorder_categories_ignores_other_users_ids(auth_client, db_session, other_user):
|
||||
other_category = journal_service.create_category(db_session, other_user.id, JournalCategoryCreate(name="남의 것"))
|
||||
|
||||
response = auth_client.post("/api/journal/categories/reorder", json={"category_ids": [other_category.id]})
|
||||
assert response.status_code == 200
|
||||
|
||||
db_session.refresh(other_category)
|
||||
assert other_category.sort_order is None
|
||||
Reference in New Issue
Block a user