Wire cljs frontend to the backend with session login

- frontend: hash routing (project list / create / editor), per-project init
  (load OTIO + scene + clip from the API), create form with otio + clip upload,
  annotation saves/deletes pushed as deltas, edit/add buttons gated on auth
- auth: own login form in cljs posting to session login/logout endpoints
  (httpOnly cookie — no JWT in JS); /api/me/ drives the UI's signed-in state
- backend: add /api/login/ and /api/logout/ (session auth)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-06-29 01:54:22 -04:00
parent 94475da936
commit 20cd3cc6e3
9 changed files with 300 additions and 80 deletions

View file

@ -4,6 +4,8 @@ from . import views
urlpatterns = [
path("me/", views.me),
path("login/", views.login_view),
path("logout/", views.logout_view),
path("projects/", views.projects),
path("projects/<int:pk>/", views.project),
path("projects/<int:pk>/scene/", views.scene),

View file

@ -1,5 +1,6 @@
import json
from django.contrib.auth import authenticate, login as dj_login, logout as dj_logout
from django.db.models import Q
from django.http import HttpResponseNotAllowed, JsonResponse
from django.shortcuts import get_object_or_404
@ -42,11 +43,34 @@ def _meta(request, project):
}
def _who(user):
return {"authenticated": user.is_authenticated,
"username": user.get_username() if user.is_authenticated else None}
def me(request):
"""GET /api/me/ — who the browser is logged in as (for the UI to gate edits)."""
u = request.user
return JsonResponse({"authenticated": u.is_authenticated,
"username": u.get_username() if u.is_authenticated else None})
return JsonResponse(_who(request.user))
@csrf_exempt
def login_view(request):
"""POST /api/login/ {username, password} — start a session (httpOnly cookie)."""
if request.method != "POST":
return HttpResponseNotAllowed(["POST"])
data = json.loads(request.body or "{}")
user = authenticate(request, username=data.get("username"), password=data.get("password"))
if user is None:
return JsonResponse({"detail": "invalid username or password"}, status=400)
dj_login(request, user)
return JsonResponse(_who(user))
@csrf_exempt
def logout_view(request):
"""POST /api/logout/ — end the session."""
dj_logout(request)
return JsonResponse(_who(request.user))
@csrf_exempt