Feature/user photo storage (#877)

* add default assets for user profile

* add recipe avatar

* change user_id to UUID

* add profile image upload

* setup image cache keys

* cleanup tests and add image tests

* purge user data on delete

* new user repository tests

* add user_id validator for int -> UUID conversion

* delete depreciated route

* force set content type

* refactor tests to use temp directory

* validate parent exists before createing

* set user_id to correct type

* update instruction id

* reset primary key on migration
This commit is contained in:
Hayden
2021-12-18 19:04:36 -09:00
committed by GitHub
parent a2f8f27193
commit ea7c4771ee
64 changed files with 433 additions and 181 deletions

View File

@@ -22,7 +22,7 @@ async def create_api_token(
):
"""Create api_token in the Database"""
token_data = {"long_token": True, "id": current_user.id}
token_data = {"long_token": True, "id": str(current_user.id)}
five_years = timedelta(1825)
token = create_access_token(token_data, five_years)
@@ -30,7 +30,7 @@ async def create_api_token(
token_model = CreateToken(
name=token_name.name,
token=token,
parent_id=current_user.id,
user_id=current_user.id,
)
db = get_database(session)

View File

@@ -1,4 +1,5 @@
from fastapi import BackgroundTasks, Depends, HTTPException, status
from pydantic import UUID4
from sqlalchemy.orm.session import Session
from mealie.core import security
@@ -39,15 +40,15 @@ async def create_user(
@admin_router.get("/{id}", response_model=UserOut)
async def get_user(id: int, session: Session = Depends(generate_session)):
async def get_user(id: UUID4, session: Session = Depends(generate_session)):
db = get_database(session)
return db.users.get(id)
@admin_router.delete("/{id}")
def delete_user(
id: UUID4,
background_tasks: BackgroundTasks,
id: int,
session: Session = Depends(generate_session),
current_user: PrivateUser = Depends(get_current_user),
):
@@ -55,7 +56,7 @@ def delete_user(
assert_user_change_allowed(id, current_user)
if id == 1:
if id == 1: # TODO: identify super_user
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="SUPER_USER")
try:
@@ -75,7 +76,7 @@ async def get_logged_in_user(
@user_router.put("/{id}")
async def update_user(
id: int,
id: UUID4,
new_data: UserBase,
current_user: PrivateUser = Depends(get_current_user),
session: Session = Depends(generate_session),

View File

@@ -1,51 +1,49 @@
import shutil
from pathlib import Path
from fastapi import Depends, File, HTTPException, UploadFile, status
from fastapi.responses import FileResponse
from fastapi.routing import APIRouter
from pydantic import UUID4
from sqlalchemy.orm.session import Session
from mealie.core.config import get_app_dirs
app_dirs = get_app_dirs()
from mealie import utils
from mealie.core.dependencies import get_current_user
from mealie.core.dependencies.dependencies import temporary_dir
from mealie.db.database import get_database
from mealie.db.db_setup import generate_session
from mealie.routes.routers import UserAPIRouter
from mealie.routes.users._helpers import assert_user_change_allowed
from mealie.schema.user import PrivateUser
from mealie.services.image import minify
public_router = APIRouter(prefix="", tags=["Users: Images"])
user_router = UserAPIRouter(prefix="", tags=["Users: Images"])
@public_router.get("/{id}/image")
async def get_user_image(id: str):
"""Returns a users profile picture"""
user_dir = app_dirs.USER_DIR.joinpath(id)
for recipe_image in user_dir.glob("profile_image.*"):
return FileResponse(recipe_image)
else:
raise HTTPException(status.HTTP_404_NOT_FOUND)
@user_router.post("/{id}/image")
def update_user_image(
id: str,
profile_image: UploadFile = File(...),
id: UUID4,
profile: UploadFile = File(...),
temp_dir: Path = Depends(temporary_dir),
current_user: PrivateUser = Depends(get_current_user),
session: Session = Depends(generate_session),
):
"""Updates a User Image"""
assert_user_change_allowed(id, current_user)
extension = profile_image.filename.split(".")[-1]
temp_img = temp_dir.joinpath(profile.filename)
app_dirs.USER_DIR.joinpath(id).mkdir(parents=True, exist_ok=True)
with temp_img.open("wb") as buffer:
shutil.copyfileobj(profile.file, buffer)
[x.unlink() for x in app_dirs.USER_DIR.joinpath(id).glob("profile_image.*")]
image = minify.to_webp(temp_img)
dest = PrivateUser.get_directory(id) / "profile.webp"
dest = app_dirs.USER_DIR.joinpath(id, f"profile_image.{extension}")
shutil.copyfile(image, dest)
with dest.open("wb") as buffer:
shutil.copyfileobj(profile_image.file, buffer)
db = get_database(session)
db.users.patch(id, {"cache_key": utils.new_cache_key()})
if not dest.is_file:
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR)