mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
refactor(backend): ♻️ organize and tag user routes by path
This commit is contained in:
parent
7d40f8d74d
commit
05f2eab1ea
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@ -1,10 +1,8 @@
|
|||||||
{
|
{
|
||||||
"python.formatting.provider": "black",
|
"python.formatting.provider": "black",
|
||||||
"python.pythonPath": ".venv/bin/python3.9",
|
"python.pythonPath": ".venv/bin/python3.9",
|
||||||
"python.linting.pylintEnabled": false,
|
|
||||||
"python.linting.enabled": true,
|
"python.linting.enabled": true,
|
||||||
"python.testing.unittestEnabled": false,
|
"python.testing.unittestEnabled": false,
|
||||||
"python.testing.nosetestsEnabled": false,
|
|
||||||
"python.testing.pytestEnabled": true,
|
"python.testing.pytestEnabled": true,
|
||||||
"python.testing.autoTestDiscoverOnSaveEnabled": false,
|
"python.testing.autoTestDiscoverOnSaveEnabled": false,
|
||||||
"python.testing.pytestArgs": ["tests"],
|
"python.testing.pytestArgs": ["tests"],
|
||||||
@ -20,7 +18,9 @@
|
|||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"eslint.workingDirectories": ["./frontend"],
|
"eslint.workingDirectories": ["./frontend"],
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true
|
"source.fixAll.eslint": true,
|
||||||
|
"source.organizeImports": false
|
||||||
},
|
},
|
||||||
"vetur.validation.template": false
|
"vetur.validation.template": false,
|
||||||
|
"python.linting.pylintEnabled": false
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ from fastapi.middleware.gzip import GZipMiddleware
|
|||||||
|
|
||||||
from mealie.core.config import APP_VERSION, settings
|
from mealie.core.config import APP_VERSION, settings
|
||||||
from mealie.core.root_logger import get_logger
|
from mealie.core.root_logger import get_logger
|
||||||
from mealie.routes import backup_routes, debug_routes, migration_routes, theme_routes, utility_routes
|
from mealie.routes import backup_routes, debug_routes, migration_routes, router, theme_routes, utility_routes
|
||||||
from mealie.routes.about import about_router
|
from mealie.routes.about import about_router
|
||||||
from mealie.routes.groups import groups_router
|
from mealie.routes.groups import groups_router
|
||||||
from mealie.routes.mealplans import meal_plan_router
|
from mealie.routes.mealplans import meal_plan_router
|
||||||
@ -12,7 +12,6 @@ from mealie.routes.media import media_router
|
|||||||
from mealie.routes.recipe import recipe_router
|
from mealie.routes.recipe import recipe_router
|
||||||
from mealie.routes.shopping_list import shopping_list_router
|
from mealie.routes.shopping_list import shopping_list_router
|
||||||
from mealie.routes.site_settings import settings_router
|
from mealie.routes.site_settings import settings_router
|
||||||
from mealie.routes.users import user_router
|
|
||||||
from mealie.services.events import create_general_event
|
from mealie.services.events import create_general_event
|
||||||
from mealie.services.recipe.all_recipes import subscripte_to_recipe_events
|
from mealie.services.recipe.all_recipes import subscripte_to_recipe_events
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ def start_scheduler():
|
|||||||
|
|
||||||
def api_routers():
|
def api_routers():
|
||||||
# Authentication
|
# Authentication
|
||||||
app.include_router(user_router)
|
app.include_router(router)
|
||||||
app.include_router(groups_router)
|
app.include_router(groups_router)
|
||||||
app.include_router(shopping_list_router)
|
app.include_router(shopping_list_router)
|
||||||
# Recipes
|
# Recipes
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from . import auth, users
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api")
|
||||||
|
|
||||||
|
router.include_router(auth.router)
|
||||||
|
router.include_router(users.router)
|
8
mealie/routes/auth/__init__.py
Normal file
8
mealie/routes/auth/__init__.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from . import auth
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/auth")
|
||||||
|
|
||||||
|
router.include_router(auth.public_router)
|
||||||
|
router.include_router(auth.user_router)
|
@ -10,8 +10,8 @@ from mealie.schema.user import UserInDB
|
|||||||
from mealie.services.events import create_user_event
|
from mealie.services.events import create_user_event
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
public_router = APIRouter(prefix="/api/auth", tags=["Authentication"])
|
public_router = APIRouter(tags=["Authentication"])
|
||||||
user_router = UserAPIRouter(prefix="/api/auth", tags=["Authentication"])
|
user_router = UserAPIRouter(tags=["Authentication"])
|
||||||
|
|
||||||
|
|
||||||
@public_router.post("/token/long")
|
@public_router.post("/token/long")
|
@ -1,6 +1,6 @@
|
|||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from fastapi.routing import APIRouter
|
from fastapi.routing import APIRouter
|
||||||
from mealie.core.config import APP_VERSION, app_dirs, settings
|
from mealie.core.config import APP_VERSION, settings
|
||||||
from mealie.core.root_logger import LOGGER_FILE
|
from mealie.core.root_logger import LOGGER_FILE
|
||||||
from mealie.core.security import create_file_token
|
from mealie.core.security import create_file_token
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
@ -50,12 +50,6 @@ async def get_mealie_version():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin_router.get("/last-recipe-json")
|
|
||||||
async def get_last_recipe_json():
|
|
||||||
""" Returns a token to download a file """
|
|
||||||
return {"fileToken": create_file_token(app_dirs.DEBUG_DIR.joinpath("last_recipe.json"))}
|
|
||||||
|
|
||||||
|
|
||||||
@admin_router.get("/log/{num}")
|
@admin_router.get("/log/{num}")
|
||||||
async def get_log(num: int):
|
async def get_log(num: int):
|
||||||
""" Doc Str """
|
""" Doc Str """
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from mealie.routes.deps import get_admin_user, get_current_user
|
from mealie.routes.deps import get_admin_user, get_current_user
|
||||||
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
|
|
||||||
class AdminAPIRouter(APIRouter):
|
class AdminAPIRouter(APIRouter):
|
||||||
""" Router for functions to be protected behind admin authentication """
|
""" Router for functions to be protected behind admin authentication """
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from . import api_tokens, auth, crud, sign_up
|
from . import api_tokens, crud, favorites, images, passwords, sign_up
|
||||||
|
|
||||||
user_router = APIRouter()
|
# Must be used because of the way FastAPI works with nested routes
|
||||||
|
user_prefix = "/users"
|
||||||
|
|
||||||
user_router.include_router(auth.public_router)
|
router = APIRouter()
|
||||||
user_router.include_router(auth.user_router)
|
|
||||||
user_router.include_router(sign_up.public_router)
|
router.include_router(sign_up.admin_router, prefix=user_prefix, tags=["Users: Sign-Up"])
|
||||||
user_router.include_router(sign_up.admin_router)
|
router.include_router(sign_up.public_router, prefix=user_prefix, tags=["Users: Sign-Up"])
|
||||||
user_router.include_router(crud.public_router)
|
|
||||||
user_router.include_router(crud.user_router)
|
router.include_router(crud.user_router, prefix=user_prefix, tags=["Users: CRUD"])
|
||||||
user_router.include_router(crud.admin_router)
|
router.include_router(crud.admin_router, prefix=user_prefix, tags=["Users: CRUD"])
|
||||||
user_router.include_router(api_tokens.router)
|
|
||||||
|
router.include_router(passwords.user_router, prefix=user_prefix, tags=["Users: Passwords"])
|
||||||
|
|
||||||
|
router.include_router(images.public_router, prefix=user_prefix, tags=["Users: Images"])
|
||||||
|
router.include_router(images.user_router, prefix=user_prefix, tags=["Users: Images"])
|
||||||
|
|
||||||
|
router.include_router(api_tokens.router, prefix=user_prefix, tags=["Users: Tokens"])
|
||||||
|
|
||||||
|
router.include_router(favorites.user_router, prefix=user_prefix, tags=["Users: Favorites"])
|
||||||
|
8
mealie/routes/users/_helpers.py
Normal file
8
mealie/routes/users/_helpers.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from fastapi import HTTPException, status
|
||||||
|
from mealie.schema.user.user import UserInDB
|
||||||
|
|
||||||
|
|
||||||
|
def assert_user_change_allowed(id: int, current_user: UserInDB):
|
||||||
|
if current_user.id != id and not current_user.admin:
|
||||||
|
# only admins can edit other users
|
||||||
|
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="NOT_AN_ADMIN")
|
@ -10,7 +10,7 @@ from mealie.routes.routers import UserAPIRouter
|
|||||||
from mealie.schema.user import CreateToken, LoingLiveTokenIn, LongLiveTokenInDB, UserInDB
|
from mealie.schema.user import CreateToken, LoingLiveTokenIn, LongLiveTokenInDB, UserInDB
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = UserAPIRouter(prefix="/api/users", tags=["User API Tokens"])
|
router = UserAPIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api-tokens", status_code=status.HTTP_201_CREATED)
|
@router.post("/api-tokens", status_code=status.HTTP_201_CREATED)
|
||||||
|
@ -1,28 +1,22 @@
|
|||||||
import shutil
|
from fastapi import BackgroundTasks, Depends, HTTPException, status
|
||||||
|
|
||||||
from fastapi import BackgroundTasks, Depends, File, HTTPException, UploadFile, status
|
|
||||||
from fastapi.responses import FileResponse
|
|
||||||
from fastapi.routing import APIRouter
|
|
||||||
from mealie.core import security
|
from mealie.core import security
|
||||||
from mealie.core.config import app_dirs, settings
|
from mealie.core.security import get_password_hash
|
||||||
from mealie.core.security import get_password_hash, verify_password
|
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
from mealie.db.db_setup import generate_session
|
from mealie.db.db_setup import generate_session
|
||||||
from mealie.routes.deps import get_current_user
|
from mealie.routes.deps import get_current_user
|
||||||
from mealie.routes.routers import AdminAPIRouter, UserAPIRouter
|
from mealie.routes.routers import AdminAPIRouter, UserAPIRouter
|
||||||
from mealie.schema.user import ChangePassword, UserBase, UserFavorites, UserIn, UserInDB, UserOut
|
from mealie.routes.users._helpers import assert_user_change_allowed
|
||||||
|
from mealie.schema.user import UserBase, UserIn, UserInDB, UserOut
|
||||||
from mealie.services.events import create_user_event
|
from mealie.services.events import create_user_event
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
public_router = APIRouter(prefix="/api/users", tags=["Users"])
|
user_router = UserAPIRouter(prefix="")
|
||||||
user_router = UserAPIRouter(prefix="/api/users", tags=["Users"])
|
admin_router = AdminAPIRouter(prefix="")
|
||||||
admin_router = AdminAPIRouter(prefix="/api/users", tags=["Users"])
|
|
||||||
|
|
||||||
|
|
||||||
def assert_user_change_allowed(id: int, current_user: UserInDB):
|
@admin_router.get("", response_model=list[UserOut])
|
||||||
if current_user.id != id and not current_user.admin:
|
async def get_all_users(session: Session = Depends(generate_session)):
|
||||||
# only admins can edit other users
|
return db.users.get_all(session)
|
||||||
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="NOT_AN_ADMIN")
|
|
||||||
|
|
||||||
|
|
||||||
@admin_router.post("", response_model=UserOut, status_code=201)
|
@admin_router.post("", response_model=UserOut, status_code=201)
|
||||||
@ -40,9 +34,33 @@ async def create_user(
|
|||||||
return db.users.create(session, new_user.dict())
|
return db.users.create(session, new_user.dict())
|
||||||
|
|
||||||
|
|
||||||
@admin_router.get("", response_model=list[UserOut])
|
@admin_router.get("/{id}", response_model=UserOut)
|
||||||
async def get_all_users(session: Session = Depends(generate_session)):
|
async def get_user(
|
||||||
return db.users.get_all(session)
|
id: int,
|
||||||
|
session: Session = Depends(generate_session),
|
||||||
|
):
|
||||||
|
return db.users.get(session, id)
|
||||||
|
|
||||||
|
|
||||||
|
@admin_router.delete("/{id}")
|
||||||
|
def delete_user(
|
||||||
|
background_tasks: BackgroundTasks,
|
||||||
|
id: int,
|
||||||
|
session: Session = Depends(generate_session),
|
||||||
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
""" Removes a user from the database. Must be the current user or a super user"""
|
||||||
|
|
||||||
|
assert_user_change_allowed(id, current_user)
|
||||||
|
|
||||||
|
if id == 1:
|
||||||
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="SUPER_USER")
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.users.delete(session, id)
|
||||||
|
background_tasks.add_task(create_user_event, "User Deleted", f"User ID: {id}", session=session)
|
||||||
|
except Exception:
|
||||||
|
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
@user_router.get("/self", response_model=UserOut)
|
@user_router.get("/self", response_model=UserOut)
|
||||||
@ -52,24 +70,6 @@ async def get_logged_in_user(
|
|||||||
return current_user.dict()
|
return current_user.dict()
|
||||||
|
|
||||||
|
|
||||||
@admin_router.get("/{id}", response_model=UserOut)
|
|
||||||
async def get_user_by_id(
|
|
||||||
id: int,
|
|
||||||
session: Session = Depends(generate_session),
|
|
||||||
):
|
|
||||||
return db.users.get(session, id)
|
|
||||||
|
|
||||||
|
|
||||||
@user_router.put("/{id}/reset-password")
|
|
||||||
async def reset_user_password(
|
|
||||||
id: int,
|
|
||||||
session: Session = Depends(generate_session),
|
|
||||||
):
|
|
||||||
|
|
||||||
new_password = get_password_hash(settings.DEFAULT_PASSWORD)
|
|
||||||
db.users.update_password(session, id, new_password)
|
|
||||||
|
|
||||||
|
|
||||||
@user_router.put("/{id}")
|
@user_router.put("/{id}")
|
||||||
async def update_user(
|
async def update_user(
|
||||||
id: int,
|
id: int,
|
||||||
@ -91,117 +91,4 @@ async def update_user(
|
|||||||
db.users.update(session, id, new_data.dict())
|
db.users.update(session, id, new_data.dict())
|
||||||
if current_user.id == id:
|
if current_user.id == id:
|
||||||
access_token = security.create_access_token(data=dict(sub=new_data.email))
|
access_token = security.create_access_token(data=dict(sub=new_data.email))
|
||||||
token = {"access_token": access_token, "token_type": "bearer"}
|
return {"access_token": access_token, "token_type": "bearer"}
|
||||||
return token
|
|
||||||
|
|
||||||
|
|
||||||
@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(...),
|
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
|
||||||
):
|
|
||||||
""" Updates a User Image """
|
|
||||||
|
|
||||||
assert_user_change_allowed(id, current_user)
|
|
||||||
|
|
||||||
extension = profile_image.filename.split(".")[-1]
|
|
||||||
|
|
||||||
app_dirs.USER_DIR.joinpath(id).mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
[x.unlink() for x in app_dirs.USER_DIR.joinpath(id).glob("profile_image.*")]
|
|
||||||
|
|
||||||
dest = app_dirs.USER_DIR.joinpath(id, f"profile_image.{extension}")
|
|
||||||
|
|
||||||
with dest.open("wb") as buffer:
|
|
||||||
shutil.copyfileobj(profile_image.file, buffer)
|
|
||||||
|
|
||||||
if not dest.is_file:
|
|
||||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
||||||
|
|
||||||
|
|
||||||
@user_router.put("/{id}/password")
|
|
||||||
def update_password(
|
|
||||||
id: int,
|
|
||||||
password_change: ChangePassword,
|
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
|
||||||
session: Session = Depends(generate_session),
|
|
||||||
):
|
|
||||||
""" Resets the User Password"""
|
|
||||||
|
|
||||||
assert_user_change_allowed(id, current_user)
|
|
||||||
match_passwords = verify_password(password_change.current_password, current_user.password)
|
|
||||||
|
|
||||||
if not (match_passwords):
|
|
||||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
new_password = get_password_hash(password_change.new_password)
|
|
||||||
db.users.update_password(session, id, new_password)
|
|
||||||
|
|
||||||
|
|
||||||
@user_router.get("/{id}/favorites", response_model=UserFavorites)
|
|
||||||
async def get_favorites(id: str, session: Session = Depends(generate_session)):
|
|
||||||
""" Get user's favorite recipes """
|
|
||||||
|
|
||||||
return db.users.get(session, id, override_schema=UserFavorites)
|
|
||||||
|
|
||||||
|
|
||||||
@user_router.post("/{id}/favorites/{slug}")
|
|
||||||
def add_favorite(
|
|
||||||
slug: str,
|
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
|
||||||
session: Session = Depends(generate_session),
|
|
||||||
):
|
|
||||||
""" Adds a Recipe to the users favorites """
|
|
||||||
|
|
||||||
assert_user_change_allowed(id, current_user)
|
|
||||||
current_user.favorite_recipes.append(slug)
|
|
||||||
|
|
||||||
db.users.update(session, current_user.id, current_user)
|
|
||||||
|
|
||||||
|
|
||||||
@user_router.delete("/{id}/favorites/{slug}")
|
|
||||||
def remove_favorite(
|
|
||||||
slug: str,
|
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
|
||||||
session: Session = Depends(generate_session),
|
|
||||||
):
|
|
||||||
""" Adds a Recipe to the users favorites """
|
|
||||||
|
|
||||||
assert_user_change_allowed(id, current_user)
|
|
||||||
current_user.favorite_recipes = [x for x in current_user.favorite_recipes if x != slug]
|
|
||||||
|
|
||||||
db.users.update(session, current_user.id, current_user)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
@admin_router.delete("/{id}")
|
|
||||||
def delete_user(
|
|
||||||
background_tasks: BackgroundTasks,
|
|
||||||
id: int,
|
|
||||||
session: Session = Depends(generate_session),
|
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
|
||||||
):
|
|
||||||
""" Removes a user from the database. Must be the current user or a super user"""
|
|
||||||
|
|
||||||
assert_user_change_allowed(id, current_user)
|
|
||||||
|
|
||||||
if id == 1:
|
|
||||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="SUPER_USER")
|
|
||||||
|
|
||||||
try:
|
|
||||||
db.users.delete(session, id)
|
|
||||||
background_tasks.add_task(create_user_event, "User Deleted", f"User ID: {id}", session=session)
|
|
||||||
except Exception:
|
|
||||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
47
mealie/routes/users/favorites.py
Normal file
47
mealie/routes/users/favorites.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
from fastapi import Depends
|
||||||
|
from mealie.db.database import db
|
||||||
|
from mealie.db.db_setup import generate_session
|
||||||
|
from mealie.routes.deps import get_current_user
|
||||||
|
from mealie.routes.routers import UserAPIRouter
|
||||||
|
from mealie.routes.users._helpers import assert_user_change_allowed
|
||||||
|
from mealie.schema.user import UserFavorites, UserInDB
|
||||||
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
|
user_router = UserAPIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@user_router.get("/{id}/favorites", response_model=UserFavorites)
|
||||||
|
async def get_favorites(id: str, session: Session = Depends(generate_session)):
|
||||||
|
""" Get user's favorite recipes """
|
||||||
|
|
||||||
|
return db.users.get(session, id, override_schema=UserFavorites)
|
||||||
|
|
||||||
|
|
||||||
|
@user_router.post("/{id}/favorites/{slug}")
|
||||||
|
def add_favorite(
|
||||||
|
slug: str,
|
||||||
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
|
session: Session = Depends(generate_session),
|
||||||
|
):
|
||||||
|
""" Adds a Recipe to the users favorites """
|
||||||
|
|
||||||
|
assert_user_change_allowed(id, current_user)
|
||||||
|
current_user.favorite_recipes.append(slug)
|
||||||
|
|
||||||
|
db.users.update(session, current_user.id, current_user)
|
||||||
|
|
||||||
|
|
||||||
|
@user_router.delete("/{id}/favorites/{slug}")
|
||||||
|
def remove_favorite(
|
||||||
|
slug: str,
|
||||||
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
|
session: Session = Depends(generate_session),
|
||||||
|
):
|
||||||
|
""" Adds a Recipe to the users favorites """
|
||||||
|
|
||||||
|
assert_user_change_allowed(id, current_user)
|
||||||
|
current_user.favorite_recipes = [x for x in current_user.favorite_recipes if x != slug]
|
||||||
|
|
||||||
|
db.users.update(session, current_user.id, current_user)
|
||||||
|
|
||||||
|
return
|
48
mealie/routes/users/images.py
Normal file
48
mealie/routes/users/images.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import shutil
|
||||||
|
|
||||||
|
from fastapi import Depends, File, HTTPException, UploadFile, status
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
|
from fastapi.routing import APIRouter
|
||||||
|
from mealie.core.config import app_dirs
|
||||||
|
from mealie.routes.deps import get_current_user
|
||||||
|
from mealie.routes.routers import UserAPIRouter
|
||||||
|
from mealie.routes.users._helpers import assert_user_change_allowed
|
||||||
|
from mealie.schema.user import UserInDB
|
||||||
|
|
||||||
|
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(...),
|
||||||
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
""" Updates a User Image """
|
||||||
|
|
||||||
|
assert_user_change_allowed(id, current_user)
|
||||||
|
|
||||||
|
extension = profile_image.filename.split(".")[-1]
|
||||||
|
|
||||||
|
app_dirs.USER_DIR.joinpath(id).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
[x.unlink() for x in app_dirs.USER_DIR.joinpath(id).glob("profile_image.*")]
|
||||||
|
|
||||||
|
dest = app_dirs.USER_DIR.joinpath(id, f"profile_image.{extension}")
|
||||||
|
|
||||||
|
with dest.open("wb") as buffer:
|
||||||
|
shutil.copyfileobj(profile_image.file, buffer)
|
||||||
|
|
||||||
|
if not dest.is_file:
|
||||||
|
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR)
|
41
mealie/routes/users/passwords.py
Normal file
41
mealie/routes/users/passwords.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
from fastapi import Depends, HTTPException, status
|
||||||
|
from mealie.core.config import settings
|
||||||
|
from mealie.core.security import get_password_hash, verify_password
|
||||||
|
from mealie.db.database import db
|
||||||
|
from mealie.db.db_setup import generate_session
|
||||||
|
from mealie.routes.deps import get_current_user
|
||||||
|
from mealie.routes.routers import UserAPIRouter
|
||||||
|
from mealie.routes.users._helpers import assert_user_change_allowed
|
||||||
|
from mealie.schema.user import ChangePassword, UserInDB
|
||||||
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
|
user_router = UserAPIRouter(prefix="")
|
||||||
|
|
||||||
|
|
||||||
|
@user_router.put("/{id}/reset-password")
|
||||||
|
async def reset_user_password(
|
||||||
|
id: int,
|
||||||
|
session: Session = Depends(generate_session),
|
||||||
|
):
|
||||||
|
|
||||||
|
new_password = get_password_hash(settings.DEFAULT_PASSWORD)
|
||||||
|
db.users.update_password(session, id, new_password)
|
||||||
|
|
||||||
|
|
||||||
|
@user_router.put("/{id}/password")
|
||||||
|
def update_password(
|
||||||
|
id: int,
|
||||||
|
password_change: ChangePassword,
|
||||||
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
|
session: Session = Depends(generate_session),
|
||||||
|
):
|
||||||
|
""" Resets the User Password"""
|
||||||
|
|
||||||
|
assert_user_change_allowed(id, current_user)
|
||||||
|
match_passwords = verify_password(password_change.current_password, current_user.password)
|
||||||
|
|
||||||
|
if not (match_passwords):
|
||||||
|
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
new_password = get_password_hash(password_change.new_password)
|
||||||
|
db.users.update_password(session, id, new_password)
|
@ -6,12 +6,13 @@ from mealie.db.database import db
|
|||||||
from mealie.db.db_setup import generate_session
|
from mealie.db.db_setup import generate_session
|
||||||
from mealie.routes.deps import get_admin_user
|
from mealie.routes.deps import get_admin_user
|
||||||
from mealie.routes.routers import AdminAPIRouter
|
from mealie.routes.routers import AdminAPIRouter
|
||||||
from mealie.schema.user import SignUpIn, SignUpOut, SignUpToken, UserIn, UserInDB
|
from mealie.schema.user import (SignUpIn, SignUpOut, SignUpToken, UserIn,
|
||||||
|
UserInDB)
|
||||||
from mealie.services.events import create_user_event
|
from mealie.services.events import create_user_event
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
public_router = APIRouter(prefix="/api/users/sign-ups", tags=["User Signup"])
|
public_router = APIRouter(prefix="/sign-ups")
|
||||||
admin_router = AdminAPIRouter(prefix="/api/users/sign-ups", tags=["User Signup"])
|
admin_router = AdminAPIRouter(prefix="/sign-ups")
|
||||||
|
|
||||||
|
|
||||||
@admin_router.get("", response_model=list[SignUpOut])
|
@admin_router.get("", response_model=list[SignUpOut])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user