- Paste-to-embed: pasting an image into the markdown editor uploads it and
inserts  at the cursor. Unlike gallery attachments these aren't
tied to a journal_entry (the entry may not exist yet while composing), so
they're stored per-user under app/media/journal/{user_id}/pasted/ with no
DB row, served through an ownership-scoped route, and never cleaned up
automatically when an entry is deleted -- an accepted tradeoff at this
app's personal scale.
- The markdown sanitizer was stripping all <img> tags (not on the bleach
allowlist), which would have silently deleted every pasted image on save;
added img/src/alt/title while keeping event-handler attributes blocked.
- Cap embedded image width in both the editor pane and the rendered preview
so a large pasted photo can't overflow its card.
- Fix real data loss risk found while testing this: docker-compose.yml had
no volume for app/media, so every container recreate during a deploy wiped
uploaded photos, and deploy_sftp.py was syncing app/media/ (runtime user
data, not source) into the remote build context. Added the volume mount
and excluded media/ from the sync script. Recovered and relocated the
real attachments that had already landed in the wrong place on the NAS
during earlier deploys this session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
57 lines
2.0 KiB
Python
57 lines
2.0 KiB
Python
from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile
|
|
from fastapi.responses import FileResponse
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.database import get_db
|
|
from app.models.user import User
|
|
from app.schemas.journal import JournalCategoryReorderRequest
|
|
from app.security import require_login
|
|
from app.services import journal_service
|
|
|
|
router = APIRouter(prefix="/api/journal", tags=["journal"], dependencies=[Depends(require_login)])
|
|
|
|
|
|
@router.get("/media/{attachment_id}")
|
|
def get_media(
|
|
attachment_id: int,
|
|
thumbnail: bool = Query(False),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_login),
|
|
):
|
|
attachment = journal_service.get_attachment(db, attachment_id, current_user.id)
|
|
if attachment is None:
|
|
raise HTTPException(status_code=404, detail="첨부파일을 찾을 수 없습니다")
|
|
|
|
path = attachment.thumbnail_path if (thumbnail and attachment.thumbnail_path) else attachment.file_path
|
|
return FileResponse(path, filename=attachment.original_filename)
|
|
|
|
|
|
@router.post("/paste-image")
|
|
def paste_image(
|
|
file: UploadFile = File(...),
|
|
current_user: User = Depends(require_login),
|
|
):
|
|
try:
|
|
filename = journal_service.save_pasted_image(current_user.id, file)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc))
|
|
return {"url": f"/api/journal/pasted-media/{filename}"}
|
|
|
|
|
|
@router.get("/pasted-media/{filename}")
|
|
def get_pasted_image(filename: str, current_user: User = Depends(require_login)):
|
|
path = journal_service.get_pasted_image_path(current_user.id, filename)
|
|
if path is None:
|
|
raise HTTPException(status_code=404, detail="이미지를 찾을 수 없습니다")
|
|
return FileResponse(path)
|
|
|
|
|
|
@router.post("/categories/reorder")
|
|
def reorder_categories(
|
|
data: JournalCategoryReorderRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_login),
|
|
):
|
|
journal_service.reorder_categories(db, current_user.id, data.category_ids)
|
|
return {"ok": True}
|