snotes updates, actions, attachments, easy user space

This commit is contained in:
2026-09-22 17:31:55 -07:00
parent ce30c6f4f7
commit abfbf3de1f
21 changed files with 669 additions and 53 deletions

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from contextlib import asynccontextmanager
import structlog
from fastapi import Depends, FastAPI, HTTPException, Request, Response
from fastapi import Depends, FastAPI, File, Form, HTTPException, Request, Response, UploadFile
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
@@ -49,6 +49,12 @@ def current_user(request: Request) -> str:
return username or settings.user_list[0]
def valid_username(username: str) -> str:
if username not in settings.user_list and username != settings.common_space:
raise HTTPException(status_code=404, detail="unknown user")
return username
# ---------------------------------------------------------------------------
# SPA + static
# ---------------------------------------------------------------------------
@@ -61,7 +67,7 @@ async def index() -> FileResponse:
@app.get("/favicon.ico", include_in_schema=False)
async def favicon() -> FileResponse:
return FileResponse("static/snotes-main.png")
return FileResponse("static/snotes-new.png")
# ---------------------------------------------------------------------------
@@ -114,6 +120,8 @@ async def login(body: LoginRequest) -> Response:
@app.post("/api/notes")
async def create_note(body: NoteCreate, request: Request, _: None = Depends(require_access)):
username = current_user(request)
if body.username:
username = valid_username(body.username)
if not body.body.strip():
raise HTTPException(status_code=400, detail="empty note")
try:
@@ -148,7 +156,13 @@ async def get_note(note_id: str, _: None = Depends(require_access)):
async def update_note(
note_id: str, body: NoteUpdate, request: Request, _: None = Depends(require_access)
):
note = await notes.update_note(note_id, body.body, ip=client_ip(request), ua=client_ua(request))
note = await notes.update_note(
note_id,
body.body,
ip=client_ip(request),
ua=client_ua(request),
username=valid_username(body.username) if body.username else None,
)
if note is None:
raise HTTPException(status_code=404, detail="note not found")
return note
@@ -168,10 +182,45 @@ async def note_history(note_id: str, _: None = Depends(require_access)):
# ---------------------------------------------------------------------------
# Tags / search
# Attachments
# ---------------------------------------------------------------------------
@app.post("/api/attachments")
async def upload_attachment(
request: Request,
file: UploadFile = File(...),
username: str | None = Form(None),
_: None = Depends(require_access),
):
owner = current_user(request)
if username:
owner = valid_username(username)
data = await file.read()
if not data:
raise HTTPException(status_code=400, detail="empty file")
filename = await notes.store_attachment(owner, file.filename or "file", data)
return {
"url": f"/api/attachments/{owner}/{filename}",
"name": file.filename or filename,
"path": f"{owner}/attachments/{filename}",
}
@app.get("/api/attachments/{username}/{filename}")
async def get_attachment(username: str, filename: str, _: None = Depends(require_access)):
from pathlib import Path
full = Path(settings.data_dir) / username / "attachments" / filename
if not full.is_file():
raise HTTPException(status_code=404, detail="not found")
return FileResponse(full)
# ---------------------------------------------------------------------------
# Tags / search
# ---------------------------------------------------------------------------
@app.get("/api/tags")
async def tags(_: None = Depends(require_access)):
return await notes.tag_counts()