feat: Ipad app features and Dream Weaver for Velocity WebOS
This commit is contained in:
105
backend/auth/routes.py
Normal file
105
backend/auth/routes.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, Request, UploadFile, status
|
||||
from pydantic import BaseModel
|
||||
|
||||
from backend.auth.dependencies import UserPrincipal, get_current_user
|
||||
from backend.auth.service import (
|
||||
list_tenant_users,
|
||||
login_with_directory,
|
||||
read_authenticated_user_profile,
|
||||
)
|
||||
from backend.auth.user_directory import ensure_user_directory_schema
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
ASSET_DIR = os.getenv("VELOCITY_ASSET_DIR", "/opt/dlami/nvme/assets")
|
||||
|
||||
|
||||
def _sanitize_filename(value: str) -> str:
|
||||
cleaned = re.sub(r"[^A-Za-z0-9._-]+", "_", value).strip("._")
|
||||
return cleaned or "upload"
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
email: str
|
||||
password: str
|
||||
|
||||
|
||||
@router.post("/api/auth/login", tags=["Auth"])
|
||||
async def login(body: LoginRequest, request: Request):
|
||||
"""
|
||||
Authenticate a user and return a JWT.
|
||||
Credentials are verified against the users_and_roles table.
|
||||
"""
|
||||
return await login_with_directory(
|
||||
app=request.app,
|
||||
email=body.email,
|
||||
password=body.password,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/auth/me", tags=["Auth"])
|
||||
async def me(request: Request, user: UserPrincipal = Depends(get_current_user)):
|
||||
return await read_authenticated_user_profile(app=request.app, user=user)
|
||||
|
||||
|
||||
@router.get("/api/auth/users", tags=["Auth"])
|
||||
async def list_auth_users(request: Request, user: UserPrincipal = Depends(get_current_user)):
|
||||
return await list_tenant_users(app=request.app, user=user)
|
||||
|
||||
|
||||
@router.post("/api/auth/profile/avatar", tags=["Auth"])
|
||||
async def upload_profile_avatar(
|
||||
request: Request,
|
||||
file: UploadFile = File(...),
|
||||
user: UserPrincipal = Depends(get_current_user),
|
||||
):
|
||||
await ensure_user_directory_schema(request.app)
|
||||
pool = getattr(request.app.state, "db_pool", None)
|
||||
if pool is None:
|
||||
raise HTTPException(status_code=503, detail="Database unavailable.")
|
||||
|
||||
allowed = {"image/png", "image/jpeg", "image/jpg", "image/webp"}
|
||||
if file.content_type not in allowed:
|
||||
raise HTTPException(status_code=400, detail="Unsupported avatar format.")
|
||||
|
||||
extension = Path(file.filename or "avatar.png").suffix.lower() or ".png"
|
||||
if extension not in {".png", ".jpg", ".jpeg", ".webp"}:
|
||||
extension = ".png"
|
||||
|
||||
avatar_dir = Path(ASSET_DIR) / "profile_avatars"
|
||||
avatar_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
filename = (
|
||||
f"{user.user_id}_{_sanitize_filename(Path(file.filename or 'avatar').stem)}_"
|
||||
f"{int(datetime.now(timezone.utc).timestamp())}{extension}"
|
||||
)
|
||||
destination = avatar_dir / filename
|
||||
contents = await file.read()
|
||||
destination.write_bytes(contents)
|
||||
|
||||
avatar_url = f"/assets/profile_avatars/{filename}"
|
||||
|
||||
async with pool.acquire() as conn:
|
||||
result = await conn.execute(
|
||||
"""
|
||||
UPDATE users_and_roles
|
||||
SET avatar_url = $2
|
||||
WHERE id = $1::uuid
|
||||
AND tenant_id = $3
|
||||
""",
|
||||
user.user_id,
|
||||
avatar_url,
|
||||
user.tenant_id,
|
||||
)
|
||||
|
||||
if result == "UPDATE 0":
|
||||
raise HTTPException(status_code=404, detail="Authenticated user profile was not found.")
|
||||
|
||||
return {"avatar_url": avatar_url}
|
||||
Reference in New Issue
Block a user