mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-05 05:25:26 +02:00
API security hardening (#571)
* Enhance security and safety around user update API - Prevent a regular user from promoting themself to admin - Prevent an admin from demoting themself - Refactor token fixture to admin + regular user tokens * Restrict user CRUD API to admins * Secure admin API routes * Refactor APIrouter into Admin/UserAPIRouter * Secure theme routes * Make 'all recipes' routes public * Secure favorite routes * Remove redundant checks * Fix public routes mistakenly flagged user routes * Make webhooks changeable only by admin * Allow users to create categories and tags * Address lint issues
This commit is contained in:
parent
f5faff66d3
commit
6320ba7ec5
43 changed files with 456 additions and 347 deletions
|
@ -42,12 +42,16 @@ def api_routers():
|
||||||
app.include_router(meal_plan_router)
|
app.include_router(meal_plan_router)
|
||||||
# Settings Routes
|
# Settings Routes
|
||||||
app.include_router(settings_router)
|
app.include_router(settings_router)
|
||||||
app.include_router(theme_routes.router)
|
app.include_router(theme_routes.public_router)
|
||||||
|
app.include_router(theme_routes.user_router)
|
||||||
# Backups/Imports Routes
|
# Backups/Imports Routes
|
||||||
app.include_router(backup_routes.router)
|
app.include_router(backup_routes.router)
|
||||||
# Migration Routes
|
# Migration Routes
|
||||||
app.include_router(migration_routes.router)
|
app.include_router(migration_routes.router)
|
||||||
app.include_router(debug_routes.router)
|
# Debug routes
|
||||||
|
app.include_router(debug_routes.public_router)
|
||||||
|
app.include_router(debug_routes.admin_router)
|
||||||
|
# Utility routes
|
||||||
app.include_router(utility_routes.router)
|
app.include_router(utility_routes.router)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,41 +1,36 @@
|
||||||
from http.client import HTTPException
|
from http.client import HTTPException
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, status
|
from fastapi import Depends, status
|
||||||
from mealie.core.root_logger import get_logger
|
from mealie.core.root_logger import get_logger
|
||||||
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.routers import AdminAPIRouter
|
||||||
from mealie.schema.event_notifications import EventNotificationIn, EventNotificationOut
|
from mealie.schema.event_notifications import EventNotificationIn, EventNotificationOut
|
||||||
from mealie.schema.events import EventsOut, TestEvent
|
from mealie.schema.events import EventsOut, TestEvent
|
||||||
from mealie.schema.user import UserInDB
|
|
||||||
from mealie.services.events import test_notification
|
from mealie.services.events import test_notification
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/events", tags=["App Events"])
|
router = AdminAPIRouter(prefix="/events", tags=["App Events"])
|
||||||
|
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=EventsOut)
|
@router.get("", response_model=EventsOut)
|
||||||
async def get_events(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)):
|
async def get_events(session: Session = Depends(generate_session)):
|
||||||
""" Get event from the Database """
|
""" Get event from the Database """
|
||||||
# Get Item
|
# Get Item
|
||||||
return EventsOut(total=db.events.count_all(session), events=db.events.get_all(session, order_by="time_stamp"))
|
return EventsOut(total=db.events.count_all(session), events=db.events.get_all(session, order_by="time_stamp"))
|
||||||
|
|
||||||
|
|
||||||
@router.delete("")
|
@router.delete("")
|
||||||
async def delete_events(
|
async def delete_events(session: Session = Depends(generate_session)):
|
||||||
session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
|
|
||||||
):
|
|
||||||
""" Get event from the Database """
|
""" Get event from the Database """
|
||||||
# Get Item
|
# Get Item
|
||||||
return db.events.delete_all(session)
|
return db.events.delete_all(session)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{id}")
|
@router.delete("/{id}")
|
||||||
async def delete_event(
|
async def delete_event(id: int, session: Session = Depends(generate_session)):
|
||||||
id: int, session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
|
|
||||||
):
|
|
||||||
""" Delete event from the Database """
|
""" Delete event from the Database """
|
||||||
return db.events.delete(session, id)
|
return db.events.delete(session, id)
|
||||||
|
|
||||||
|
@ -44,7 +39,6 @@ async def delete_event(
|
||||||
async def create_event_notification(
|
async def create_event_notification(
|
||||||
event_data: EventNotificationIn,
|
event_data: EventNotificationIn,
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
|
||||||
):
|
):
|
||||||
""" Create event_notification in the Database """
|
""" Create event_notification in the Database """
|
||||||
|
|
||||||
|
@ -55,7 +49,6 @@ async def create_event_notification(
|
||||||
async def test_notification_route(
|
async def test_notification_route(
|
||||||
test_data: TestEvent,
|
test_data: TestEvent,
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
|
||||||
):
|
):
|
||||||
""" Create event_notification in the Database """
|
""" Create event_notification in the Database """
|
||||||
|
|
||||||
|
@ -71,27 +64,21 @@ async def test_notification_route(
|
||||||
|
|
||||||
|
|
||||||
@router.get("/notifications", response_model=list[EventNotificationOut])
|
@router.get("/notifications", response_model=list[EventNotificationOut])
|
||||||
async def get_all_event_notification(
|
async def get_all_event_notification(session: Session = Depends(generate_session)):
|
||||||
session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
|
|
||||||
):
|
|
||||||
""" Get all event_notification from the Database """
|
""" Get all event_notification from the Database """
|
||||||
# Get Item
|
# Get Item
|
||||||
return db.event_notifications.get_all(session, override_schema=EventNotificationOut)
|
return db.event_notifications.get_all(session, override_schema=EventNotificationOut)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/notifications/{id}")
|
@router.put("/notifications/{id}")
|
||||||
async def update_event_notification(
|
async def update_event_notification(id: int, session: Session = Depends(generate_session)):
|
||||||
id: int, session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
|
|
||||||
):
|
|
||||||
""" Update event_notification in the Database """
|
""" Update event_notification in the Database """
|
||||||
# Update Item
|
# not yet implemented
|
||||||
return {"details": "not yet implemented"}
|
raise HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/notifications/{id}")
|
@router.delete("/notifications/{id}")
|
||||||
async def delete_event_notification(
|
async def delete_event_notification(id: int, session: Session = Depends(generate_session)):
|
||||||
id: int, session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
|
|
||||||
):
|
|
||||||
""" Delete event_notification from the Database """
|
""" Delete event_notification from the Database """
|
||||||
# Delete Item
|
# Delete Item
|
||||||
return db.event_notifications.delete(session, id)
|
return db.event_notifications.delete(session, id)
|
||||||
|
|
|
@ -2,19 +2,19 @@ import operator
|
||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import APIRouter, BackgroundTasks, Depends, File, HTTPException, UploadFile, status
|
from fastapi import BackgroundTasks, Depends, File, HTTPException, UploadFile, status
|
||||||
from mealie.core.config import app_dirs
|
from mealie.core.config import app_dirs
|
||||||
from mealie.core.root_logger import get_logger
|
from mealie.core.root_logger import get_logger
|
||||||
from mealie.core.security import create_file_token
|
from mealie.core.security import create_file_token
|
||||||
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.routers import AdminAPIRouter
|
||||||
from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup
|
from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup
|
||||||
from mealie.services.backups import imports
|
from mealie.services.backups import imports
|
||||||
from mealie.services.backups.exports import backup_all
|
from mealie.services.backups.exports import backup_all
|
||||||
from mealie.services.events import create_backup_event
|
from mealie.services.events import create_backup_event
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/backups", tags=["Backups"], dependencies=[Depends(get_current_user)])
|
router = AdminAPIRouter(prefix="/api/backups", tags=["Backups"])
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import Depends
|
||||||
|
from fastapi.routing import APIRouter
|
||||||
from mealie.core.config import APP_VERSION, app_dirs, settings
|
from mealie.core.config import APP_VERSION, app_dirs, 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
|
||||||
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.routers import AdminAPIRouter
|
||||||
from mealie.schema.about import AppInfo, AppStatistics, DebugInfo
|
from mealie.schema.about import AppInfo, AppStatistics, DebugInfo
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/debug", tags=["Debug"])
|
|
||||||
|
admin_router = AdminAPIRouter(prefix="/api/debug", tags=["Debug"])
|
||||||
|
public_router = APIRouter(prefix="/api/debug", tags=["Debug"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("")
|
@admin_router.get("")
|
||||||
async def get_debug_info(current_user=Depends(get_current_user)):
|
async def get_debug_info():
|
||||||
""" Returns general information about the application for debugging """
|
""" Returns general information about the application for debugging """
|
||||||
|
|
||||||
return DebugInfo(
|
return DebugInfo(
|
||||||
|
@ -27,7 +30,7 @@ async def get_debug_info(current_user=Depends(get_current_user)):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/statistics")
|
@admin_router.get("/statistics")
|
||||||
async def get_app_statistics(session: Session = Depends(generate_session)):
|
async def get_app_statistics(session: Session = Depends(generate_session)):
|
||||||
return AppStatistics(
|
return AppStatistics(
|
||||||
total_recipes=db.recipes.count_all(session),
|
total_recipes=db.recipes.count_all(session),
|
||||||
|
@ -38,7 +41,7 @@ async def get_app_statistics(session: Session = Depends(generate_session)):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/version")
|
@public_router.get("/version")
|
||||||
async def get_mealie_version():
|
async def get_mealie_version():
|
||||||
""" Returns the current version of mealie"""
|
""" Returns the current version of mealie"""
|
||||||
return AppInfo(
|
return AppInfo(
|
||||||
|
@ -48,21 +51,21 @@ async def get_mealie_version():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/last-recipe-json")
|
@admin_router.get("/last-recipe-json")
|
||||||
async def get_last_recipe_json(current_user=Depends(get_current_user)):
|
async def get_last_recipe_json():
|
||||||
""" Returns a token to download a file """
|
""" Returns a token to download a file """
|
||||||
return {"fileToken": create_file_token(app_dirs.DEBUG_DIR.joinpath("last_recipe.json"))}
|
return {"fileToken": create_file_token(app_dirs.DEBUG_DIR.joinpath("last_recipe.json"))}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/log/{num}")
|
@admin_router.get("/log/{num}")
|
||||||
async def get_log(num: int, current_user=Depends(get_current_user)):
|
async def get_log(num: int):
|
||||||
""" Doc Str """
|
""" Doc Str """
|
||||||
with open(LOGGER_FILE, "rb") as f:
|
with open(LOGGER_FILE, "rb") as f:
|
||||||
log_text = tail(f, num)
|
log_text = tail(f, num)
|
||||||
return log_text
|
return log_text
|
||||||
|
|
||||||
|
|
||||||
@router.get("/log")
|
@admin_router.get("/log")
|
||||||
async def get_log_file():
|
async def get_log_file():
|
||||||
""" Returns a token to download a file """
|
""" Returns a token to download a file """
|
||||||
return {"fileToken": create_file_token(LOGGER_FILE)}
|
return {"fileToken": create_file_token(LOGGER_FILE)}
|
||||||
|
|
|
@ -75,6 +75,12 @@ async def get_current_user(token: str = Depends(oauth2_scheme), session=Depends(
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
async def get_admin_user(current_user=Depends(get_current_user)) -> UserInDB:
|
||||||
|
if not current_user.admin:
|
||||||
|
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||||
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
def validate_long_live_token(session: Session, client_token: str, id: int) -> UserInDB:
|
def validate_long_live_token(session: Session, client_token: str, id: int) -> UserInDB:
|
||||||
|
|
||||||
tokens: list[LongLiveTokenInDB] = db.api_tokens.get(session, id, "parent_id", limit=9999)
|
tokens: list[LongLiveTokenInDB] = db.api_tokens.get(session, id, "parent_id", limit=9999)
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from . import crud, groups
|
from . import groups
|
||||||
|
|
||||||
groups_router = APIRouter()
|
groups_router = APIRouter()
|
||||||
|
|
||||||
groups_router.include_router(crud.router)
|
|
||||||
groups_router.include_router(groups.router)
|
groups_router.include_router(groups.router)
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
|
from fastapi import BackgroundTasks, Depends, HTTPException, status
|
||||||
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.schema.user import GroupBase, GroupInDB, UpdateGroup, UserInDB
|
from mealie.schema.user import GroupBase, GroupInDB, UpdateGroup, UserInDB
|
||||||
from mealie.services.events import create_group_event
|
from mealie.services.events import create_group_event
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/groups", tags=["Groups"])
|
admin_router = AdminAPIRouter(prefix="/api/groups", tags=["Groups administration"])
|
||||||
|
user_router = UserAPIRouter(prefix="/api/groups", tags=["Groups"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=list[GroupInDB])
|
@admin_router.get("", response_model=list[GroupInDB])
|
||||||
async def get_all_groups(
|
async def get_all_groups(
|
||||||
current_user=Depends(get_current_user),
|
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
):
|
):
|
||||||
""" Returns a list of all groups in the database """
|
""" Returns a list of all groups in the database """
|
||||||
|
@ -19,7 +20,7 @@ async def get_all_groups(
|
||||||
return db.groups.get_all(session)
|
return db.groups.get_all(session)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/self", response_model=GroupInDB)
|
@user_router.get("/self", response_model=GroupInDB)
|
||||||
async def get_current_user_group(
|
async def get_current_user_group(
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
|
@ -30,11 +31,10 @@ async def get_current_user_group(
|
||||||
return db.groups.get(session, current_user.group, "name")
|
return db.groups.get(session, current_user.group, "name")
|
||||||
|
|
||||||
|
|
||||||
@router.post("", status_code=status.HTTP_201_CREATED)
|
@admin_router.post("", status_code=status.HTTP_201_CREATED)
|
||||||
async def create_group(
|
async def create_group(
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
group_data: GroupBase,
|
group_data: GroupBase,
|
||||||
current_user=Depends(get_current_user),
|
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
):
|
):
|
||||||
""" Creates a Group in the Database """
|
""" Creates a Group in the Database """
|
||||||
|
@ -46,18 +46,17 @@ async def create_group(
|
||||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{id}")
|
@admin_router.put("/{id}")
|
||||||
async def update_group_data(
|
async def update_group_data(
|
||||||
id: int,
|
id: int,
|
||||||
group_data: UpdateGroup,
|
group_data: UpdateGroup,
|
||||||
current_user=Depends(get_current_user),
|
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
):
|
):
|
||||||
""" Updates a User Group """
|
""" Updates a User Group """
|
||||||
db.groups.update(session, id, group_data.dict())
|
db.groups.update(session, id, group_data.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{id}")
|
@admin_router.delete("/{id}")
|
||||||
async def delete_user_group(
|
async def delete_user_group(
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
id: int,
|
id: int,
|
||||||
|
|
|
@ -3,4 +3,5 @@ from mealie.routes.groups import crud
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
router.include_router(crud.router)
|
router.include_router(crud.admin_router)
|
||||||
|
router.include_router(crud.user_router)
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
|
from fastapi import BackgroundTasks, Depends, HTTPException, status
|
||||||
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 UserAPIRouter
|
||||||
from mealie.schema.meal import MealPlanIn, MealPlanOut
|
from mealie.schema.meal import MealPlanIn, MealPlanOut
|
||||||
from mealie.schema.user import GroupInDB, UserInDB
|
from mealie.schema.user import GroupInDB, UserInDB
|
||||||
from mealie.services.events import create_group_event
|
from mealie.services.events import create_group_event
|
||||||
|
@ -10,7 +11,7 @@ from mealie.services.meal_services import get_todays_meal, set_mealplan_dates
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
from starlette.responses import FileResponse
|
from starlette.responses import FileResponse
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
|
router = UserAPIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/all", response_model=list[MealPlanOut])
|
@router.get("/all", response_model=list[MealPlanOut])
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import Depends
|
||||||
from mealie.core.root_logger import get_logger
|
from mealie.core.root_logger import get_logger
|
||||||
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 UserAPIRouter
|
||||||
from mealie.schema.meal import MealPlanOut
|
from mealie.schema.meal import MealPlanOut
|
||||||
from mealie.schema.recipe import Recipe
|
from mealie.schema.recipe import Recipe
|
||||||
from mealie.schema.shopping_list import ListItem, ShoppingListIn, ShoppingListOut
|
from mealie.schema.shopping_list import ListItem, ShoppingListIn, ShoppingListOut
|
||||||
|
@ -11,7 +12,7 @@ from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
|
router = UserAPIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{id}/shopping-list")
|
@router.get("/{id}/shopping-list")
|
||||||
|
|
|
@ -2,16 +2,15 @@ import operator
|
||||||
import shutil
|
import shutil
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, File, UploadFile, status
|
from fastapi import Depends, File, HTTPException, UploadFile, status
|
||||||
from mealie.core.config import app_dirs
|
from mealie.core.config import app_dirs
|
||||||
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.routers import AdminAPIRouter
|
||||||
from mealie.schema.migration import MigrationFile, Migrations
|
from mealie.schema.migration import MigrationFile, Migrations
|
||||||
from mealie.services.migrations import migration
|
from mealie.services.migrations import migration
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
from fastapi import HTTPException
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/migrations", tags=["Migration"], dependencies=[Depends(get_current_user)])
|
router = AdminAPIRouter(prefix="/api/migrations", tags=["Migration"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=List[Migrations])
|
@router.get("", response_model=List[Migrations])
|
||||||
|
|
|
@ -4,7 +4,12 @@ from mealie.routes.recipe import all_recipe_routes, category_routes, comments, r
|
||||||
recipe_router = APIRouter()
|
recipe_router = APIRouter()
|
||||||
|
|
||||||
recipe_router.include_router(all_recipe_routes.router)
|
recipe_router.include_router(all_recipe_routes.router)
|
||||||
recipe_router.include_router(recipe_crud_routes.router)
|
recipe_router.include_router(recipe_crud_routes.public_router)
|
||||||
recipe_router.include_router(category_routes.router)
|
recipe_router.include_router(recipe_crud_routes.user_router)
|
||||||
recipe_router.include_router(tag_routes.router)
|
recipe_router.include_router(category_routes.public_router)
|
||||||
|
recipe_router.include_router(category_routes.user_router)
|
||||||
|
recipe_router.include_router(category_routes.admin_router)
|
||||||
|
recipe_router.include_router(tag_routes.admin_router)
|
||||||
|
recipe_router.include_router(tag_routes.user_router)
|
||||||
|
recipe_router.include_router(tag_routes.public_router)
|
||||||
recipe_router.include_router(comments.router)
|
recipe_router.include_router(comments.router)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
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, is_logged_in
|
from mealie.routes.deps import is_logged_in
|
||||||
from mealie.schema.recipe import RecipeSummary
|
from mealie.schema.recipe import RecipeSummary
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
@ -36,21 +36,17 @@ async def get_recipe_summary(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get("/api/recipes/summary/untagged", response_model=list[RecipeSummary])
|
||||||
"/api/recipes/summary/untagged", response_model=list[RecipeSummary], dependencies=[Depends(get_current_user)]
|
|
||||||
)
|
|
||||||
async def get_untagged_recipes(count: bool = False, session: Session = Depends(generate_session)):
|
async def get_untagged_recipes(count: bool = False, session: Session = Depends(generate_session)):
|
||||||
return db.recipes.count_untagged(session, count=count, override_schema=RecipeSummary)
|
return db.recipes.count_untagged(session, count=count, override_schema=RecipeSummary)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get("/api/recipes/summary/uncategorized", response_model=list[RecipeSummary])
|
||||||
"/api/recipes/summary/uncategorized", response_model=list[RecipeSummary], dependencies=[Depends(get_current_user)]
|
|
||||||
)
|
|
||||||
async def get_uncategorized_recipes(count: bool = False, session: Session = Depends(generate_session)):
|
async def get_uncategorized_recipes(count: bool = False, session: Session = Depends(generate_session)):
|
||||||
return db.recipes.count_uncategorized(session, count=count, override_schema=RecipeSummary)
|
return db.recipes.count_uncategorized(session, count=count, override_schema=RecipeSummary)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/recipes/category", deprecated=True, dependencies=[Depends(get_current_user)])
|
@router.post("/api/recipes/category", deprecated=True)
|
||||||
def filter_by_category(categories: list, session: Session = Depends(generate_session)):
|
def filter_by_category(categories: list, session: Session = Depends(generate_session)):
|
||||||
""" pass a list of categories and get a list of recipes associated with those categories """
|
""" pass a list of categories and get a list of recipes associated with those categories """
|
||||||
# ! This should be refactored into a single database call, but I couldn't figure it out
|
# ! This should be refactored into a single database call, but I couldn't figure it out
|
||||||
|
@ -60,7 +56,7 @@ def filter_by_category(categories: list, session: Session = Depends(generate_ses
|
||||||
return in_category
|
return in_category
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/recipes/tag", deprecated=True, dependencies=[Depends(get_current_user)])
|
@router.post("/api/recipes/tag", deprecated=True)
|
||||||
async def filter_by_tags(tags: list, session: Session = Depends(generate_session)):
|
async def filter_by_tags(tags: list, session: Session = Depends(generate_session)):
|
||||||
""" pass a list of tags and get a list of recipes associated with those tags"""
|
""" pass a list of tags and get a list of recipes associated with those tags"""
|
||||||
# ! This should be refactored into a single database call, but I couldn't figure it out
|
# ! This should be refactored into a single database call, but I couldn't figure it out
|
||||||
|
|
|
@ -1,26 +1,29 @@
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
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, is_logged_in
|
from mealie.routes.deps import is_logged_in
|
||||||
|
from mealie.routes.routers import AdminAPIRouter, UserAPIRouter
|
||||||
from mealie.schema.category import CategoryIn, RecipeCategoryResponse
|
from mealie.schema.category import CategoryIn, RecipeCategoryResponse
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/categories", tags=["Recipe Categories"])
|
public_router = APIRouter(prefix="/api/categories", tags=["Recipe Categories"])
|
||||||
|
user_router = UserAPIRouter(prefix="/api/categories", tags=["Recipe Categories"])
|
||||||
|
admin_router = AdminAPIRouter(prefix="/api/categories", tags=["Recipe Categories"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("")
|
@public_router.get("")
|
||||||
async def get_all_recipe_categories(session: Session = Depends(generate_session)):
|
async def get_all_recipe_categories(session: Session = Depends(generate_session)):
|
||||||
""" Returns a list of available categories in the database """
|
""" Returns a list of available categories in the database """
|
||||||
return db.categories.get_all_limit_columns(session, ["slug", "name"])
|
return db.categories.get_all_limit_columns(session, ["slug", "name"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/empty")
|
@public_router.get("/empty")
|
||||||
def get_empty_categories(session: Session = Depends(generate_session)):
|
def get_empty_categories(session: Session = Depends(generate_session)):
|
||||||
""" Returns a list of categories that do not contain any recipes"""
|
""" Returns a list of categories that do not contain any recipes"""
|
||||||
return db.categories.get_empty(session)
|
return db.categories.get_empty(session)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{category}", response_model=RecipeCategoryResponse)
|
@public_router.get("/{category}", response_model=RecipeCategoryResponse)
|
||||||
def get_all_recipes_by_category(
|
def get_all_recipes_by_category(
|
||||||
category: str, session: Session = Depends(generate_session), is_user: bool = Depends(is_logged_in)
|
category: str, session: Session = Depends(generate_session), is_user: bool = Depends(is_logged_in)
|
||||||
):
|
):
|
||||||
|
@ -35,7 +38,7 @@ def get_all_recipes_by_category(
|
||||||
return category_obj
|
return category_obj
|
||||||
|
|
||||||
|
|
||||||
@router.post("", dependencies=[Depends(get_current_user)])
|
@user_router.post("")
|
||||||
async def create_recipe_category(category: CategoryIn, session: Session = Depends(generate_session)):
|
async def create_recipe_category(category: CategoryIn, session: Session = Depends(generate_session)):
|
||||||
""" Creates a Category in the database """
|
""" Creates a Category in the database """
|
||||||
|
|
||||||
|
@ -45,7 +48,7 @@ async def create_recipe_category(category: CategoryIn, session: Session = Depend
|
||||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{category}", response_model=RecipeCategoryResponse, dependencies=[Depends(get_current_user)])
|
@admin_router.put("/{category}", response_model=RecipeCategoryResponse)
|
||||||
async def update_recipe_category(category: str, new_category: CategoryIn, session: Session = Depends(generate_session)):
|
async def update_recipe_category(category: str, new_category: CategoryIn, session: Session = Depends(generate_session)):
|
||||||
""" Updates an existing Tag in the database """
|
""" Updates an existing Tag in the database """
|
||||||
|
|
||||||
|
@ -55,7 +58,7 @@ async def update_recipe_category(category: str, new_category: CategoryIn, sessio
|
||||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{category}", dependencies=[Depends(get_current_user)])
|
@admin_router.delete("/{category}")
|
||||||
async def delete_recipe_category(category: str, session: Session = Depends(generate_session)):
|
async def delete_recipe_category(category: str, session: Session = Depends(generate_session)):
|
||||||
"""
|
"""
|
||||||
Removes a recipe category from the database. Deleting a
|
Removes a recipe category from the database. Deleting a
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
from http.client import HTTPException
|
from http.client import HTTPException
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, status
|
from fastapi import Depends, status
|
||||||
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 UserAPIRouter
|
||||||
from mealie.schema.comments import CommentIn, CommentOut, CommentSaveToDB
|
from mealie.schema.comments import CommentIn, CommentOut, CommentSaveToDB
|
||||||
from mealie.schema.user import UserInDB
|
from mealie.schema.user import UserInDB
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/api", tags=["Recipe Comments"])
|
router = UserAPIRouter(prefix="/api", tags=["Recipe Comments"])
|
||||||
|
|
||||||
|
|
||||||
@router.post("/recipes/{slug}/comments")
|
@router.post("/recipes/{slug}/comments")
|
||||||
|
@ -35,7 +36,7 @@ async def update_comment(
|
||||||
old_comment: CommentOut = db.comments.get(session, id)
|
old_comment: CommentOut = db.comments.get(session, id)
|
||||||
|
|
||||||
if current_user.id != old_comment.user.id:
|
if current_user.id != old_comment.user.id:
|
||||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED)
|
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
return db.comments.update(session, id, new_comment)
|
return db.comments.update(session, id, new_comment)
|
||||||
|
|
||||||
|
@ -51,4 +52,4 @@ async def delete_comment(
|
||||||
db.comments.delete(session, id)
|
db.comments.delete(session, id)
|
||||||
return
|
return
|
||||||
|
|
||||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED)
|
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import json
|
import json
|
||||||
|
from mealie.routes.routers import UserAPIRouter
|
||||||
import shutil
|
import shutil
|
||||||
from shutil import copyfileobj
|
from shutil import copyfileobj
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
@ -21,11 +22,12 @@ from slugify import slugify
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
from starlette.responses import FileResponse
|
from starlette.responses import FileResponse
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/recipes", tags=["Recipe CRUD"])
|
user_router = UserAPIRouter(prefix="/api/recipes", tags=["Recipe CRUD"])
|
||||||
|
public_router = APIRouter(prefix="/api/recipes", tags=["Recipe CRUD"])
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/create", status_code=201, response_model=str)
|
@user_router.post("/create", status_code=201, response_model=str)
|
||||||
def create_from_json(
|
def create_from_json(
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
data: Recipe,
|
data: Recipe,
|
||||||
|
@ -46,12 +48,12 @@ def create_from_json(
|
||||||
return recipe.slug
|
return recipe.slug
|
||||||
|
|
||||||
|
|
||||||
@router.post("/test-scrape-url", dependencies=[Depends(get_current_user)])
|
@user_router.post("/test-scrape-url")
|
||||||
def test_parse_recipe_url(url: RecipeURLIn):
|
def test_parse_recipe_url(url: RecipeURLIn):
|
||||||
return scrape_url(url.url)
|
return scrape_url(url.url)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/create-url", status_code=201, response_model=str)
|
@user_router.post("/create-url", status_code=201, response_model=str)
|
||||||
def parse_recipe_url(
|
def parse_recipe_url(
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
url: RecipeURLIn,
|
url: RecipeURLIn,
|
||||||
|
@ -74,7 +76,7 @@ def parse_recipe_url(
|
||||||
return recipe.slug
|
return recipe.slug
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{recipe_slug}", response_model=Recipe)
|
@public_router.get("/{recipe_slug}", response_model=Recipe)
|
||||||
def get_recipe(recipe_slug: str, session: Session = Depends(generate_session), is_user: bool = Depends(is_logged_in)):
|
def get_recipe(recipe_slug: str, session: Session = Depends(generate_session), is_user: bool = Depends(is_logged_in)):
|
||||||
""" Takes in a recipe slug, returns all data for a recipe """
|
""" Takes in a recipe slug, returns all data for a recipe """
|
||||||
|
|
||||||
|
@ -88,10 +90,10 @@ def get_recipe(recipe_slug: str, session: Session = Depends(generate_session), i
|
||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, {"details": "unauthorized"})
|
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/create-from-zip", dependencies=[Depends(get_current_user)])
|
@user_router.post("/create-from-zip")
|
||||||
async def create_recipe_from_zip(
|
async def create_recipe_from_zip(
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
temp_path=Depends(temporary_zip_path),
|
temp_path=Depends(temporary_zip_path),
|
||||||
|
@ -121,7 +123,7 @@ async def create_recipe_from_zip(
|
||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{recipe_slug}/zip")
|
@public_router.get("/{recipe_slug}/zip")
|
||||||
async def get_recipe_as_zip(
|
async def get_recipe_as_zip(
|
||||||
recipe_slug: str, session: Session = Depends(generate_session), temp_path=Depends(temporary_zip_path)
|
recipe_slug: str, session: Session = Depends(generate_session), temp_path=Depends(temporary_zip_path)
|
||||||
):
|
):
|
||||||
|
@ -139,7 +141,7 @@ async def get_recipe_as_zip(
|
||||||
return FileResponse(temp_path, filename=f"{recipe_slug}.zip")
|
return FileResponse(temp_path, filename=f"{recipe_slug}.zip")
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{recipe_slug}", dependencies=[Depends(get_current_user)])
|
@user_router.put("/{recipe_slug}")
|
||||||
def update_recipe(
|
def update_recipe(
|
||||||
recipe_slug: str,
|
recipe_slug: str,
|
||||||
data: Recipe,
|
data: Recipe,
|
||||||
|
@ -154,7 +156,7 @@ def update_recipe(
|
||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/{recipe_slug}", dependencies=[Depends(get_current_user)])
|
@user_router.patch("/{recipe_slug}")
|
||||||
def patch_recipe(
|
def patch_recipe(
|
||||||
recipe_slug: str,
|
recipe_slug: str,
|
||||||
data: Recipe,
|
data: Recipe,
|
||||||
|
@ -171,7 +173,7 @@ def patch_recipe(
|
||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{recipe_slug}")
|
@user_router.delete("/{recipe_slug}")
|
||||||
def delete_recipe(
|
def delete_recipe(
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
recipe_slug: str,
|
recipe_slug: str,
|
||||||
|
@ -194,7 +196,7 @@ def delete_recipe(
|
||||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{recipe_slug}/image", dependencies=[Depends(get_current_user)])
|
@user_router.put("/{recipe_slug}/image")
|
||||||
def update_recipe_image(
|
def update_recipe_image(
|
||||||
recipe_slug: str,
|
recipe_slug: str,
|
||||||
image: bytes = File(...),
|
image: bytes = File(...),
|
||||||
|
@ -208,7 +210,7 @@ def update_recipe_image(
|
||||||
return {"image": new_version}
|
return {"image": new_version}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{recipe_slug}/image", dependencies=[Depends(get_current_user)])
|
@user_router.post("/{recipe_slug}/image")
|
||||||
def scrape_image_url(
|
def scrape_image_url(
|
||||||
recipe_slug: str,
|
recipe_slug: str,
|
||||||
url: RecipeURLIn,
|
url: RecipeURLIn,
|
||||||
|
@ -218,7 +220,7 @@ def scrape_image_url(
|
||||||
scrape_image(url.url, recipe_slug)
|
scrape_image(url.url, recipe_slug)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{recipe_slug}/assets", response_model=RecipeAsset, dependencies=[Depends(get_current_user)])
|
@user_router.post("/{recipe_slug}/assets", response_model=RecipeAsset)
|
||||||
def upload_recipe_asset(
|
def upload_recipe_asset(
|
||||||
recipe_slug: str,
|
recipe_slug: str,
|
||||||
name: str = Form(...),
|
name: str = Form(...),
|
||||||
|
|
|
@ -1,28 +1,29 @@
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
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, is_logged_in
|
from mealie.routes.deps import is_logged_in
|
||||||
|
from mealie.routes.routers import AdminAPIRouter, UserAPIRouter
|
||||||
from mealie.schema.category import RecipeTagResponse, TagIn
|
from mealie.schema.category import RecipeTagResponse, TagIn
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(tags=["Recipes"])
|
public_router = APIRouter(prefix="/api/tags", tags=["Recipe Tags"])
|
||||||
|
user_router = UserAPIRouter(prefix="/api/tags", tags=["Recipe Tags"])
|
||||||
router = APIRouter(prefix="/api/tags", tags=["Recipe Tags"])
|
admin_router = AdminAPIRouter(prefix="/api/tags", tags=["Recipe Tags"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("")
|
@public_router.get("")
|
||||||
async def get_all_recipe_tags(session: Session = Depends(generate_session)):
|
async def get_all_recipe_tags(session: Session = Depends(generate_session)):
|
||||||
""" Returns a list of available tags in the database """
|
""" Returns a list of available tags in the database """
|
||||||
return db.tags.get_all_limit_columns(session, ["slug", "name"])
|
return db.tags.get_all_limit_columns(session, ["slug", "name"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/empty")
|
@public_router.get("/empty")
|
||||||
def get_empty_tags(session: Session = Depends(generate_session)):
|
def get_empty_tags(session: Session = Depends(generate_session)):
|
||||||
""" Returns a list of tags that do not contain any recipes"""
|
""" Returns a list of tags that do not contain any recipes"""
|
||||||
return db.tags.get_empty(session)
|
return db.tags.get_empty(session)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{tag}", response_model=RecipeTagResponse)
|
@public_router.get("/{tag}", response_model=RecipeTagResponse)
|
||||||
def get_all_recipes_by_tag(
|
def get_all_recipes_by_tag(
|
||||||
tag: str, session: Session = Depends(generate_session), is_user: bool = Depends(is_logged_in)
|
tag: str, session: Session = Depends(generate_session), is_user: bool = Depends(is_logged_in)
|
||||||
):
|
):
|
||||||
|
@ -36,21 +37,21 @@ def get_all_recipes_by_tag(
|
||||||
return tag_obj
|
return tag_obj
|
||||||
|
|
||||||
|
|
||||||
@router.post("", dependencies=[Depends(get_current_user)])
|
@user_router.post("")
|
||||||
async def create_recipe_tag(tag: TagIn, session: Session = Depends(generate_session)):
|
async def create_recipe_tag(tag: TagIn, session: Session = Depends(generate_session)):
|
||||||
""" Creates a Tag in the database """
|
""" Creates a Tag in the database """
|
||||||
|
|
||||||
return db.tags.create(session, tag.dict())
|
return db.tags.create(session, tag.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{tag}", response_model=RecipeTagResponse, dependencies=[Depends(get_current_user)])
|
@admin_router.put("/{tag}", response_model=RecipeTagResponse)
|
||||||
async def update_recipe_tag(tag: str, new_tag: TagIn, session: Session = Depends(generate_session)):
|
async def update_recipe_tag(tag: str, new_tag: TagIn, session: Session = Depends(generate_session)):
|
||||||
""" Updates an existing Tag in the database """
|
""" Updates an existing Tag in the database """
|
||||||
|
|
||||||
return db.tags.update(session, tag, new_tag.dict())
|
return db.tags.update(session, tag, new_tag.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{tag}", dependencies=[Depends(get_current_user)])
|
@admin_router.delete("/{tag}")
|
||||||
async def delete_recipe_tag(tag: str, session: Session = Depends(generate_session)):
|
async def delete_recipe_tag(tag: str, session: Session = Depends(generate_session)):
|
||||||
"""Removes a recipe tag from the database. Deleting a
|
"""Removes a recipe tag from the database. Deleting a
|
||||||
tag does not impact a recipe. The tag will be removed
|
tag does not impact a recipe. The tag will be removed
|
||||||
|
|
26
mealie/routes/routers.py
Normal file
26
mealie/routes/routers.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from mealie.routes.deps import get_admin_user, get_current_user
|
||||||
|
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class AdminAPIRouter(APIRouter):
|
||||||
|
""" Router for functions to be protected behind admin authentication """
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
tags: Optional[List[str]] = None,
|
||||||
|
prefix: str = "",
|
||||||
|
):
|
||||||
|
super().__init__(tags=tags, prefix=prefix, dependencies=[Depends(get_admin_user)])
|
||||||
|
|
||||||
|
|
||||||
|
class UserAPIRouter(APIRouter):
|
||||||
|
""" Router for functions to be protected behind user authentication """
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
tags: Optional[List[str]] = None,
|
||||||
|
prefix: str = "",
|
||||||
|
):
|
||||||
|
super().__init__(tags=tags, prefix=prefix, dependencies=[Depends(get_current_user)])
|
|
@ -1,12 +1,13 @@
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import Depends
|
||||||
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 UserAPIRouter
|
||||||
from mealie.schema.shopping_list import ShoppingListIn, ShoppingListOut
|
from mealie.schema.shopping_list import ShoppingListIn, ShoppingListOut
|
||||||
from mealie.schema.user import UserInDB
|
from mealie.schema.user import UserInDB
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
shopping_list_router = APIRouter(prefix="/api/shopping-lists", tags=["Shopping Lists"])
|
shopping_list_router = UserAPIRouter(prefix="/api/shopping-lists", tags=["Shopping Lists"])
|
||||||
|
|
||||||
|
|
||||||
@shopping_list_router.post("", response_model=ShoppingListOut)
|
@shopping_list_router.post("", response_model=ShoppingListOut)
|
||||||
|
@ -28,13 +29,13 @@ async def get_shopping_list(id: int, session: Session = Depends(generate_session
|
||||||
return db.shopping_lists.get(session, id)
|
return db.shopping_lists.get(session, id)
|
||||||
|
|
||||||
|
|
||||||
@shopping_list_router.put("/{id}", dependencies=[Depends(get_current_user)], response_model=ShoppingListOut)
|
@shopping_list_router.put("/{id}", response_model=ShoppingListOut)
|
||||||
async def update_shopping_list(id: int, new_data: ShoppingListIn, session: Session = Depends(generate_session)):
|
async def update_shopping_list(id: int, new_data: ShoppingListIn, session: Session = Depends(generate_session)):
|
||||||
""" Update Shopping List in the Database """
|
""" Update Shopping List in the Database """
|
||||||
return db.shopping_lists.update(session, id, new_data)
|
return db.shopping_lists.update(session, id, new_data)
|
||||||
|
|
||||||
|
|
||||||
@shopping_list_router.delete("/{id}", dependencies=[Depends(get_current_user)])
|
@shopping_list_router.delete("/{id}")
|
||||||
async def delete_shopping_list(id: int, session: Session = Depends(generate_session)):
|
async def delete_shopping_list(id: int, session: Session = Depends(generate_session)):
|
||||||
""" Delete Shopping List from the Database """
|
""" Delete Shopping List from the Database """
|
||||||
return db.shopping_lists.delete(session, id)
|
return db.shopping_lists.delete(session, id)
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from . import all_settings, custom_pages, site_settings
|
from . import custom_pages, site_settings
|
||||||
|
|
||||||
settings_router = APIRouter()
|
settings_router = APIRouter()
|
||||||
|
|
||||||
settings_router.include_router(all_settings.router)
|
settings_router.include_router(custom_pages.public_router)
|
||||||
settings_router.include_router(custom_pages.router)
|
settings_router.include_router(custom_pages.admin_router)
|
||||||
settings_router.include_router(site_settings.router)
|
settings_router.include_router(site_settings.public_router)
|
||||||
|
settings_router.include_router(site_settings.admin_router)
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
from fastapi import APIRouter
|
|
||||||
from mealie.routes.site_settings import custom_pages, site_settings
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
router.include_router(custom_pages.router)
|
|
||||||
router.include_router(site_settings.router)
|
|
|
@ -1,46 +1,41 @@
|
||||||
|
from mealie.routes.routers import AdminAPIRouter
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
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.schema.settings import CustomPageBase, CustomPageOut
|
from mealie.schema.settings import CustomPageBase, CustomPageOut
|
||||||
from mealie.schema.user import UserInDB
|
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/site-settings/custom-pages", tags=["Settings"])
|
public_router = APIRouter(prefix="/api/site-settings/custom-pages", tags=["Settings"])
|
||||||
|
admin_router = AdminAPIRouter(prefix="/api/site-settings/custom-pages", tags=["Settings"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("")
|
@public_router.get("")
|
||||||
def get_custom_pages(session: Session = Depends(generate_session)):
|
def get_custom_pages(session: Session = Depends(generate_session)):
|
||||||
""" Returns the sites custom pages """
|
""" Returns the sites custom pages """
|
||||||
|
|
||||||
return db.custom_pages.get_all(session)
|
return db.custom_pages.get_all(session)
|
||||||
|
|
||||||
|
|
||||||
@router.post("")
|
@admin_router.post("")
|
||||||
async def create_new_page(
|
async def create_new_page(
|
||||||
new_page: CustomPageBase,
|
new_page: CustomPageBase,
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
|
||||||
):
|
):
|
||||||
""" Creates a new Custom Page """
|
""" Creates a new Custom Page """
|
||||||
|
|
||||||
db.custom_pages.create(session, new_page.dict())
|
db.custom_pages.create(session, new_page.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.put("")
|
@admin_router.put("")
|
||||||
async def update_multiple_pages(
|
async def update_multiple_pages(pages: list[CustomPageOut], session: Session = Depends(generate_session)):
|
||||||
pages: list[CustomPageOut],
|
|
||||||
session: Session = Depends(generate_session),
|
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
|
||||||
):
|
|
||||||
""" Update multiple custom pages """
|
""" Update multiple custom pages """
|
||||||
for page in pages:
|
for page in pages:
|
||||||
db.custom_pages.update(session, page.id, page.dict())
|
db.custom_pages.update(session, page.id, page.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{id}")
|
@public_router.get("/{id}")
|
||||||
async def get_single_page(
|
async def get_single_page(
|
||||||
id: Union[int, str],
|
id: Union[int, str],
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
|
@ -52,23 +47,21 @@ async def get_single_page(
|
||||||
return db.custom_pages.get(session, id, "slug")
|
return db.custom_pages.get(session, id, "slug")
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{id}")
|
@admin_router.put("/{id}")
|
||||||
async def update_single_page(
|
async def update_single_page(
|
||||||
data: CustomPageOut,
|
data: CustomPageOut,
|
||||||
id: int,
|
id: int,
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
current_user=Depends(get_current_user),
|
|
||||||
):
|
):
|
||||||
""" Removes a custom page from the database """
|
""" Removes a custom page from the database """
|
||||||
|
|
||||||
return db.custom_pages.update(session, id, data.dict())
|
return db.custom_pages.update(session, id, data.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{id}")
|
@admin_router.delete("/{id}")
|
||||||
async def delete_custom_page(
|
async def delete_custom_page(
|
||||||
id: int,
|
id: int,
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
|
||||||
):
|
):
|
||||||
""" Removes a custom page from the database """
|
""" Removes a custom page from the database """
|
||||||
|
|
||||||
|
|
|
@ -2,22 +2,24 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
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
|
||||||
from mealie.schema.settings import SiteSettings
|
from mealie.schema.settings import SiteSettings
|
||||||
from mealie.schema.user import GroupInDB, UserInDB
|
from mealie.schema.user import GroupInDB, UserInDB
|
||||||
from mealie.utils.post_webhooks import post_webhooks
|
from mealie.utils.post_webhooks import post_webhooks
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/site-settings", tags=["Settings"])
|
public_router = APIRouter(prefix="/api/site-settings", tags=["Settings"])
|
||||||
|
admin_router = AdminAPIRouter(prefix="/api/site-settings", tags=["Settings"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("")
|
@public_router.get("")
|
||||||
def get_main_settings(session: Session = Depends(generate_session)):
|
def get_main_settings(session: Session = Depends(generate_session)):
|
||||||
""" Returns basic site settings """
|
""" Returns basic site settings """
|
||||||
|
|
||||||
return db.settings.get(session, 1)
|
return db.settings.get(session, 1)
|
||||||
|
|
||||||
|
|
||||||
@router.put("", dependencies=[Depends(get_current_user)])
|
@admin_router.put("")
|
||||||
def update_settings(
|
def update_settings(
|
||||||
data: SiteSettings,
|
data: SiteSettings,
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
|
@ -26,7 +28,7 @@ def update_settings(
|
||||||
db.settings.update(session, 1, data.dict())
|
db.settings.update(session, 1, data.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.post("/webhooks/test")
|
@admin_router.post("/webhooks/test")
|
||||||
def test_webhooks(
|
def test_webhooks(
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
|
|
|
@ -1,45 +1,46 @@
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi.routing import APIRouter
|
||||||
|
from fastapi import Depends, HTTPException, status
|
||||||
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.routers import UserAPIRouter
|
||||||
from mealie.schema.theme import SiteTheme
|
from mealie.schema.theme import SiteTheme
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/api", tags=["Themes"])
|
user_router = UserAPIRouter(prefix="/api", tags=["Themes"])
|
||||||
|
public_router = APIRouter(prefix="/api", tags=["Themes"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/themes")
|
@public_router.get("/themes")
|
||||||
def get_all_themes(session: Session = Depends(generate_session)):
|
def get_all_themes(session: Session = Depends(generate_session)):
|
||||||
""" Returns all site themes """
|
""" Returns all site themes """
|
||||||
|
|
||||||
return db.themes.get_all(session)
|
return db.themes.get_all(session)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/themes/create", status_code=status.HTTP_201_CREATED)
|
@user_router.post("/themes/create", status_code=status.HTTP_201_CREATED)
|
||||||
def create_theme(data: SiteTheme, session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
|
def create_theme(data: SiteTheme, session: Session = Depends(generate_session)):
|
||||||
""" Creates a site color theme database entry """
|
""" Creates a site color theme database entry """
|
||||||
db.themes.create(session, data.dict())
|
db.themes.create(session, data.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.get("/themes/{id}")
|
@public_router.get("/themes/{id}")
|
||||||
def get_single_theme(id: int, session: Session = Depends(generate_session)):
|
def get_single_theme(id: int, session: Session = Depends(generate_session)):
|
||||||
""" Returns a named theme """
|
""" Returns a named theme """
|
||||||
return db.themes.get(session, id)
|
return db.themes.get(session, id)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/themes/{id}", status_code=status.HTTP_200_OK)
|
@user_router.put("/themes/{id}", status_code=status.HTTP_200_OK)
|
||||||
def update_theme(
|
def update_theme(
|
||||||
id: int,
|
id: int,
|
||||||
data: SiteTheme,
|
data: SiteTheme,
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
current_user=Depends(get_current_user),
|
|
||||||
):
|
):
|
||||||
""" Update a theme database entry """
|
""" Update a theme database entry """
|
||||||
db.themes.update(session, id, data.dict())
|
db.themes.update(session, id, data.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/themes/{id}", status_code=status.HTTP_200_OK)
|
@user_router.delete("/themes/{id}", status_code=status.HTTP_200_OK)
|
||||||
def delete_theme(id: int, session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
|
def delete_theme(id: int, session: Session = Depends(generate_session)):
|
||||||
""" Deletes theme from the database """
|
""" Deletes theme from the database """
|
||||||
try:
|
try:
|
||||||
db.themes.delete(session, id)
|
db.themes.delete(session, id)
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
from fastapi import APIRouter, Depends
|
from mealie.routes.routers import UserAPIRouter
|
||||||
from mealie.core.root_logger import get_logger
|
from mealie.core.root_logger import get_logger
|
||||||
from mealie.routes.deps import get_current_user
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/foods", dependencies=[Depends(get_current_user)])
|
router = UserAPIRouter(prefix="/api/foods")
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
from fastapi import APIRouter, Depends
|
from mealie.routes.routers import UserAPIRouter
|
||||||
from mealie.core.root_logger import get_logger
|
from mealie.core.root_logger import get_logger
|
||||||
from mealie.routes.deps import get_current_user
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/units", dependencies=[Depends(get_current_user)])
|
router = UserAPIRouter(prefix="/api/units")
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,11 @@ from . import api_tokens, auth, crud, sign_up
|
||||||
|
|
||||||
user_router = APIRouter()
|
user_router = APIRouter()
|
||||||
|
|
||||||
user_router.include_router(auth.router)
|
user_router.include_router(auth.public_router)
|
||||||
user_router.include_router(sign_up.router)
|
user_router.include_router(auth.user_router)
|
||||||
user_router.include_router(crud.router)
|
user_router.include_router(sign_up.public_router)
|
||||||
|
user_router.include_router(sign_up.admin_router)
|
||||||
|
user_router.include_router(crud.public_router)
|
||||||
|
user_router.include_router(crud.user_router)
|
||||||
|
user_router.include_router(crud.admin_router)
|
||||||
user_router.include_router(api_tokens.router)
|
user_router.include_router(api_tokens.router)
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
from fastapi.param_functions import Depends
|
from fastapi.param_functions import Depends
|
||||||
from mealie.core.security import create_access_token
|
from mealie.core.security import create_access_token
|
||||||
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 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 = APIRouter(prefix="/api/users", tags=["User API Tokens"])
|
router = UserAPIRouter(prefix="/api/users", tags=["User API Tokens"])
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api-tokens", status_code=status.HTTP_201_CREATED)
|
@router.post("/api-tokens", status_code=status.HTTP_201_CREATED)
|
||||||
|
@ -53,4 +54,4 @@ async def delete_api_token(
|
||||||
deleted_token = db.api_tokens.delete(session, token_id)
|
deleted_token = db.api_tokens.delete(session, token_id)
|
||||||
return {"token_delete": deleted_token.name}
|
return {"token_delete": deleted_token.name}
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED)
|
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||||
|
|
|
@ -5,15 +5,17 @@ from mealie.core import security
|
||||||
from mealie.core.security import authenticate_user
|
from mealie.core.security import authenticate_user
|
||||||
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 UserAPIRouter
|
||||||
from mealie.schema.user import UserInDB
|
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
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/auth", tags=["Authentication"])
|
public_router = APIRouter(prefix="/api/auth", tags=["Authentication"])
|
||||||
|
user_router = UserAPIRouter(prefix="/api/auth", tags=["Authentication"])
|
||||||
|
|
||||||
|
|
||||||
@router.post("/token/long")
|
@public_router.post("/token/long")
|
||||||
@router.post("/token")
|
@public_router.post("/token")
|
||||||
def get_token(
|
def get_token(
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
request: Request,
|
request: Request,
|
||||||
|
@ -38,7 +40,7 @@ def get_token(
|
||||||
return {"access_token": access_token, "token_type": "bearer"}
|
return {"access_token": access_token, "token_type": "bearer"}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/refresh")
|
@user_router.get("/refresh")
|
||||||
async def refresh_token(current_user: UserInDB = Depends(get_current_user)):
|
async def refresh_token(current_user: UserInDB = Depends(get_current_user)):
|
||||||
""" Use a valid token to get another token"""
|
""" Use a valid token to get another token"""
|
||||||
access_token = security.create_access_token(data=dict(sub=current_user.email))
|
access_token = security.create_access_token(data=dict(sub=current_user.email))
|
||||||
|
|
|
@ -1,21 +1,34 @@
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from fastapi import APIRouter, BackgroundTasks, Depends, File, HTTPException, UploadFile, status
|
from fastapi import BackgroundTasks, Depends, File, HTTPException, UploadFile, status
|
||||||
from fastapi.responses import FileResponse
|
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.config import app_dirs, settings
|
||||||
from mealie.core.security import get_password_hash, verify_password
|
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.schema.user import ChangePassword, UserBase, UserFavorites, UserIn, UserInDB, UserOut
|
from mealie.schema.user import ChangePassword, UserBase, UserFavorites, 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
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/users", tags=["Users"])
|
public_router = APIRouter(prefix="/api/users", tags=["Users"])
|
||||||
|
user_router = UserAPIRouter(prefix="/api/users", tags=["Users"])
|
||||||
|
admin_router = AdminAPIRouter(prefix="/api/users", tags=["Users"])
|
||||||
|
|
||||||
|
|
||||||
@router.post("", response_model=UserOut, status_code=201)
|
async def assert_user_change_allowed(
|
||||||
|
id: int,
|
||||||
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
@admin_router.post("", response_model=UserOut, status_code=201)
|
||||||
async def create_user(
|
async def create_user(
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
new_user: UserIn,
|
new_user: UserIn,
|
||||||
|
@ -30,26 +43,19 @@ async def create_user(
|
||||||
return db.users.create(session, new_user.dict())
|
return db.users.create(session, new_user.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=list[UserOut])
|
@admin_router.get("", response_model=list[UserOut])
|
||||||
async def get_all_users(
|
async def get_all_users(session: Session = Depends(generate_session)):
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
|
||||||
session: Session = Depends(generate_session),
|
|
||||||
):
|
|
||||||
|
|
||||||
if not current_user.admin:
|
|
||||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
return db.users.get_all(session)
|
return db.users.get_all(session)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/self", response_model=UserOut)
|
@user_router.get("/self", response_model=UserOut)
|
||||||
async def get_logged_in_user(
|
async def get_logged_in_user(
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
return current_user.dict()
|
return current_user.dict()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{id}", response_model=UserOut, dependencies=[Depends(get_current_user)])
|
@admin_router.get("/{id}", response_model=UserOut)
|
||||||
async def get_user_by_id(
|
async def get_user_by_id(
|
||||||
id: int,
|
id: int,
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
|
@ -57,7 +63,7 @@ async def get_user_by_id(
|
||||||
return db.users.get(session, id)
|
return db.users.get(session, id)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{id}/reset-password", dependencies=[Depends(get_current_user)])
|
@user_router.put("/{id}/reset-password")
|
||||||
async def reset_user_password(
|
async def reset_user_password(
|
||||||
id: int,
|
id: int,
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
|
@ -67,7 +73,7 @@ async def reset_user_password(
|
||||||
db.users.update_password(session, id, new_password)
|
db.users.update_password(session, id, new_password)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{id}")
|
@user_router.put("/{id}")
|
||||||
async def update_user(
|
async def update_user(
|
||||||
id: int,
|
id: int,
|
||||||
new_data: UserBase,
|
new_data: UserBase,
|
||||||
|
@ -75,8 +81,16 @@ async def update_user(
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
):
|
):
|
||||||
|
|
||||||
token = None
|
assert_user_change_allowed(id)
|
||||||
if current_user.id == id or current_user.admin:
|
|
||||||
|
if not current_user.admin and (new_data.admin or current_user.group != new_data.group):
|
||||||
|
# prevent a regular user from doing admin tasks on themself
|
||||||
|
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
if current_user.id == id and current_user.admin and not new_data.admin:
|
||||||
|
# prevent an admin from demoting themself
|
||||||
|
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
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))
|
||||||
|
@ -84,7 +98,7 @@ async def update_user(
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{id}/image")
|
@public_router.get("/{id}/image")
|
||||||
async def get_user_image(id: str):
|
async def get_user_image(id: str):
|
||||||
""" Returns a users profile picture """
|
""" Returns a users profile picture """
|
||||||
user_dir = app_dirs.USER_DIR.joinpath(id)
|
user_dir = app_dirs.USER_DIR.joinpath(id)
|
||||||
|
@ -94,13 +108,15 @@ async def get_user_image(id: str):
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{id}/image", dependencies=[Depends(get_current_user)])
|
@user_router.post("/{id}/image")
|
||||||
async def update_user_image(
|
async def update_user_image(
|
||||||
id: str,
|
id: str,
|
||||||
profile_image: UploadFile = File(...),
|
profile_image: UploadFile = File(...),
|
||||||
):
|
):
|
||||||
""" Updates a User Image """
|
""" Updates a User Image """
|
||||||
|
|
||||||
|
assert_user_change_allowed(id)
|
||||||
|
|
||||||
extension = profile_image.filename.split(".")[-1]
|
extension = profile_image.filename.split(".")[-1]
|
||||||
|
|
||||||
app_dirs.USER_DIR.joinpath(id).mkdir(parents=True, exist_ok=True)
|
app_dirs.USER_DIR.joinpath(id).mkdir(parents=True, exist_ok=True)
|
||||||
|
@ -116,7 +132,7 @@ async def update_user_image(
|
||||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR)
|
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{id}/password")
|
@user_router.put("/{id}/password")
|
||||||
async def update_password(
|
async def update_password(
|
||||||
id: int,
|
id: int,
|
||||||
password_change: ChangePassword,
|
password_change: ChangePassword,
|
||||||
|
@ -125,24 +141,24 @@ async def update_password(
|
||||||
):
|
):
|
||||||
""" Resets the User Password"""
|
""" Resets the User Password"""
|
||||||
|
|
||||||
|
assert_user_change_allowed(id)
|
||||||
match_passwords = verify_password(password_change.current_password, current_user.password)
|
match_passwords = verify_password(password_change.current_password, current_user.password)
|
||||||
match_id = current_user.id == id
|
|
||||||
|
|
||||||
if not (match_passwords and match_id):
|
if not (match_passwords):
|
||||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED)
|
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
new_password = get_password_hash(password_change.new_password)
|
new_password = get_password_hash(password_change.new_password)
|
||||||
db.users.update_password(session, id, new_password)
|
db.users.update_password(session, id, new_password)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{id}/favorites", response_model=UserFavorites)
|
@user_router.get("/{id}/favorites", response_model=UserFavorites)
|
||||||
async def get_favorites(id: str, session: Session = Depends(generate_session)):
|
async def get_favorites(id: str, session: Session = Depends(generate_session)):
|
||||||
""" Adds a Recipe to the users favorites """
|
""" Get user's favorite recipes """
|
||||||
|
|
||||||
return db.users.get(session, id, override_schema=UserFavorites)
|
return db.users.get(session, id, override_schema=UserFavorites)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{id}/favorites/{slug}")
|
@user_router.post("/{id}/favorites/{slug}")
|
||||||
async def add_favorite(
|
async def add_favorite(
|
||||||
slug: str,
|
slug: str,
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
|
@ -150,12 +166,13 @@ async def add_favorite(
|
||||||
):
|
):
|
||||||
""" Adds a Recipe to the users favorites """
|
""" Adds a Recipe to the users favorites """
|
||||||
|
|
||||||
|
assert_user_change_allowed(id)
|
||||||
current_user.favorite_recipes.append(slug)
|
current_user.favorite_recipes.append(slug)
|
||||||
|
|
||||||
db.users.update(session, current_user.id, current_user)
|
db.users.update(session, current_user.id, current_user)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{id}/favorites/{slug}")
|
@user_router.delete("/{id}/favorites/{slug}")
|
||||||
async def remove_favorite(
|
async def remove_favorite(
|
||||||
slug: str,
|
slug: str,
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
|
@ -163,6 +180,7 @@ async def remove_favorite(
|
||||||
):
|
):
|
||||||
""" Adds a Recipe to the users favorites """
|
""" Adds a Recipe to the users favorites """
|
||||||
|
|
||||||
|
assert_user_change_allowed(id)
|
||||||
current_user.favorite_recipes = [x for x in current_user.favorite_recipes if x != slug]
|
current_user.favorite_recipes = [x for x in current_user.favorite_recipes if x != slug]
|
||||||
|
|
||||||
db.users.update(session, current_user.id, current_user)
|
db.users.update(session, current_user.id, current_user)
|
||||||
|
@ -170,19 +188,19 @@ async def remove_favorite(
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{id}")
|
@admin_router.delete("/{id}")
|
||||||
async def delete_user(
|
async def delete_user(
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
id: int,
|
id: int,
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
):
|
):
|
||||||
""" Removes a user from the database. Must be the current user or a super user"""
|
""" Removes a user from the database. Must be the current user or a super user"""
|
||||||
|
|
||||||
|
assert_user_change_allowed(id)
|
||||||
|
|
||||||
if id == 1:
|
if id == 1:
|
||||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="SUPER_USER")
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="SUPER_USER")
|
||||||
|
|
||||||
if current_user.id == id or current_user.admin:
|
|
||||||
try:
|
try:
|
||||||
db.users.delete(session, id)
|
db.users.delete(session, id)
|
||||||
background_tasks.add_task(create_user_event, "User Deleted", f"User ID: {id}", session=session)
|
background_tasks.add_task(create_user_event, "User Deleted", f"User ID: {id}", session=session)
|
||||||
|
|
|
@ -4,18 +4,19 @@ from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
|
||||||
from mealie.core.security import get_password_hash
|
from mealie.core.security import get_password_hash
|
||||||
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_admin_user
|
||||||
|
from mealie.routes.routers import AdminAPIRouter
|
||||||
from mealie.schema.sign_up import SignUpIn, SignUpOut, SignUpToken
|
from mealie.schema.sign_up import SignUpIn, SignUpOut, SignUpToken
|
||||||
from mealie.schema.user import UserIn, UserInDB
|
from mealie.schema.user import 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
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/users/sign-ups", tags=["User Signup"])
|
public_router = APIRouter(prefix="/api/users/sign-ups", tags=["User Signup"])
|
||||||
|
admin_router = AdminAPIRouter(prefix="/api/users/sign-ups", tags=["User Signup"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=list[SignUpOut])
|
@admin_router.get("", response_model=list[SignUpOut])
|
||||||
async def get_all_open_sign_ups(
|
async def get_all_open_sign_ups(
|
||||||
current_user=Depends(get_current_user),
|
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
):
|
):
|
||||||
""" Returns a list of open sign up links """
|
""" Returns a list of open sign up links """
|
||||||
|
@ -23,18 +24,15 @@ async def get_all_open_sign_ups(
|
||||||
return db.sign_ups.get_all(session)
|
return db.sign_ups.get_all(session)
|
||||||
|
|
||||||
|
|
||||||
@router.post("", response_model=SignUpToken)
|
@admin_router.post("", response_model=SignUpToken)
|
||||||
async def create_user_sign_up_key(
|
async def create_user_sign_up_key(
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
key_data: SignUpIn,
|
key_data: SignUpIn,
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
current_user: UserInDB = Depends(get_admin_user),
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
):
|
):
|
||||||
""" Generates a Random Token that a new user can sign up with """
|
""" Generates a Random Token that a new user can sign up with """
|
||||||
|
|
||||||
if not current_user.admin:
|
|
||||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
sign_up = {
|
sign_up = {
|
||||||
"token": str(uuid.uuid1().hex),
|
"token": str(uuid.uuid1().hex),
|
||||||
"name": key_data.name,
|
"name": key_data.name,
|
||||||
|
@ -47,7 +45,7 @@ async def create_user_sign_up_key(
|
||||||
return db.sign_ups.create(session, sign_up)
|
return db.sign_ups.create(session, sign_up)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{token}")
|
@public_router.post("/{token}")
|
||||||
async def create_user_with_token(
|
async def create_user_with_token(
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
token: str,
|
token: str,
|
||||||
|
@ -59,7 +57,7 @@ async def create_user_with_token(
|
||||||
# Validate Token
|
# Validate Token
|
||||||
db_entry: SignUpOut = db.sign_ups.get(session, token, limit=1)
|
db_entry: SignUpOut = db.sign_ups.get(session, token, limit=1)
|
||||||
if not db_entry:
|
if not db_entry:
|
||||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED)
|
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
# Create User
|
# Create User
|
||||||
new_user.admin = db_entry.admin
|
new_user.admin = db_entry.admin
|
||||||
|
@ -73,14 +71,10 @@ async def create_user_with_token(
|
||||||
db.sign_ups.delete(session, token)
|
db.sign_ups.delete(session, token)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{token}")
|
@admin_router.delete("/{token}")
|
||||||
async def delete_token(
|
async def delete_token(
|
||||||
token: str,
|
token: str,
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
):
|
):
|
||||||
""" Removed a token from the database """
|
""" Removed a token from the database """
|
||||||
if not current_user.admin:
|
|
||||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
db.sign_ups.delete(session, token)
|
db.sign_ups.delete(session, token)
|
||||||
|
|
|
@ -49,16 +49,39 @@ def test_image_png():
|
||||||
return TEST_DATA.joinpath("images", "test_image.png")
|
return TEST_DATA.joinpath("images", "test_image.png")
|
||||||
|
|
||||||
|
|
||||||
@fixture(scope="session")
|
def login(form_data, api_client: requests, api_routes: AppRoutes):
|
||||||
def token(api_client: requests, api_routes: AppRoutes):
|
|
||||||
form_data = {"username": "changeme@email.com", "password": settings.DEFAULT_PASSWORD}
|
|
||||||
response = api_client.post(api_routes.auth_token, form_data)
|
response = api_client.post(api_routes.auth_token, form_data)
|
||||||
|
assert response.status_code == 200
|
||||||
token = json.loads(response.text).get("access_token")
|
token = json.loads(response.text).get("access_token")
|
||||||
|
|
||||||
return {"Authorization": f"Bearer {token}"}
|
return {"Authorization": f"Bearer {token}"}
|
||||||
|
|
||||||
|
|
||||||
|
@fixture(scope="session")
|
||||||
|
def admin_token(api_client: requests, api_routes: AppRoutes):
|
||||||
|
form_data = {"username": "changeme@email.com", "password": settings.DEFAULT_PASSWORD}
|
||||||
|
return login(form_data, api_client, api_routes)
|
||||||
|
|
||||||
|
|
||||||
|
@fixture(scope="session")
|
||||||
|
def user_token(admin_token, api_client: requests, api_routes: AppRoutes):
|
||||||
|
# Create the user
|
||||||
|
create_data = {
|
||||||
|
"fullName": "User",
|
||||||
|
"email": "user@email.com",
|
||||||
|
"password": "useruser",
|
||||||
|
"group": "Home",
|
||||||
|
"admin": False,
|
||||||
|
"tokens": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
response = api_client.post(api_routes.users, json=create_data, headers=admin_token)
|
||||||
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
# Log in as this user
|
||||||
|
form_data = {"username": "user@email.com", "password": "useruser"}
|
||||||
|
return login(form_data, api_client, api_routes)
|
||||||
|
|
||||||
|
|
||||||
@fixture(scope="session")
|
@fixture(scope="session")
|
||||||
def raw_recipe():
|
def raw_recipe():
|
||||||
return get_raw_recipe()
|
return get_raw_recipe()
|
||||||
|
|
|
@ -10,35 +10,35 @@ recipe_test_data = get_recipe_test_cases()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
||||||
def test_create_by_url(api_client: TestClient, api_routes: AppRoutes, recipe_data: RecipeSiteTestCase, token):
|
def test_create_by_url(api_client: TestClient, api_routes: AppRoutes, recipe_data: RecipeSiteTestCase, user_token):
|
||||||
api_client.delete(api_routes.recipes_recipe_slug(recipe_data.expected_slug), headers=token)
|
api_client.delete(api_routes.recipes_recipe_slug(recipe_data.expected_slug), headers=user_token)
|
||||||
|
|
||||||
response = api_client.post(api_routes.recipes_create_url, json={"url": recipe_data.url}, headers=token)
|
response = api_client.post(api_routes.recipes_create_url, json={"url": recipe_data.url}, headers=user_token)
|
||||||
|
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
assert json.loads(response.text) == recipe_data.expected_slug
|
assert json.loads(response.text) == recipe_data.expected_slug
|
||||||
|
|
||||||
|
|
||||||
def test_create_by_json(api_client: TestClient, api_routes: AppRoutes, token, raw_recipe):
|
def test_create_by_json(api_client: TestClient, api_routes: AppRoutes, user_token, raw_recipe):
|
||||||
recipe_url = api_routes.recipes_recipe_slug("banana-bread")
|
recipe_url = api_routes.recipes_recipe_slug("banana-bread")
|
||||||
api_client.delete(recipe_url, headers=token)
|
api_client.delete(recipe_url, headers=user_token)
|
||||||
response = api_client.post(api_routes.recipes_create, json=raw_recipe, headers=token)
|
response = api_client.post(api_routes.recipes_create, json=raw_recipe, headers=user_token)
|
||||||
|
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
assert json.loads(response.text) == "banana-bread"
|
assert json.loads(response.text) == "banana-bread"
|
||||||
|
|
||||||
|
|
||||||
def test_create_no_image(api_client: TestClient, api_routes: AppRoutes, token, raw_recipe_no_image):
|
def test_create_no_image(api_client: TestClient, api_routes: AppRoutes, user_token, raw_recipe_no_image):
|
||||||
response = api_client.post(api_routes.recipes_create, json=raw_recipe_no_image, headers=token)
|
response = api_client.post(api_routes.recipes_create, json=raw_recipe_no_image, headers=user_token)
|
||||||
|
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
assert json.loads(response.text) == "banana-bread-no-image"
|
assert json.loads(response.text) == "banana-bread-no-image"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
||||||
def test_read_update(api_client: TestClient, api_routes: AppRoutes, recipe_data: RecipeSiteTestCase, token):
|
def test_read_update(api_client: TestClient, api_routes: AppRoutes, recipe_data: RecipeSiteTestCase, user_token):
|
||||||
recipe_url = api_routes.recipes_recipe_slug(recipe_data.expected_slug)
|
recipe_url = api_routes.recipes_recipe_slug(recipe_data.expected_slug)
|
||||||
response = api_client.get(recipe_url, headers=token)
|
response = api_client.get(recipe_url, headers=user_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
recipe = json.loads(response.text)
|
recipe = json.loads(response.text)
|
||||||
|
@ -54,7 +54,7 @@ def test_read_update(api_client: TestClient, api_routes: AppRoutes, recipe_data:
|
||||||
test_categories = ["one", "two", "three"]
|
test_categories = ["one", "two", "three"]
|
||||||
recipe["recipeCategory"] = test_categories
|
recipe["recipeCategory"] = test_categories
|
||||||
|
|
||||||
response = api_client.put(recipe_url, json=recipe, headers=token)
|
response = api_client.put(recipe_url, json=recipe, headers=user_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert json.loads(response.text).get("slug") == recipe_data.expected_slug
|
assert json.loads(response.text).get("slug") == recipe_data.expected_slug
|
||||||
|
@ -69,9 +69,9 @@ def test_read_update(api_client: TestClient, api_routes: AppRoutes, recipe_data:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
||||||
def test_rename(api_client: TestClient, api_routes: AppRoutes, recipe_data: RecipeSiteTestCase, token):
|
def test_rename(api_client: TestClient, api_routes: AppRoutes, recipe_data: RecipeSiteTestCase, user_token):
|
||||||
recipe_url = api_routes.recipes_recipe_slug(recipe_data.expected_slug)
|
recipe_url = api_routes.recipes_recipe_slug(recipe_data.expected_slug)
|
||||||
response = api_client.get(recipe_url, headers=token)
|
response = api_client.get(recipe_url, headers=user_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
recipe = json.loads(response.text)
|
recipe = json.loads(response.text)
|
||||||
|
@ -79,7 +79,7 @@ def test_rename(api_client: TestClient, api_routes: AppRoutes, recipe_data: Reci
|
||||||
new_slug = slugify(new_name)
|
new_slug = slugify(new_name)
|
||||||
recipe["name"] = new_name
|
recipe["name"] = new_name
|
||||||
|
|
||||||
response = api_client.put(recipe_url, json=recipe, headers=token)
|
response = api_client.put(recipe_url, json=recipe, headers=user_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert json.loads(response.text).get("slug") == new_slug
|
assert json.loads(response.text).get("slug") == new_slug
|
||||||
|
@ -88,7 +88,7 @@ def test_rename(api_client: TestClient, api_routes: AppRoutes, recipe_data: Reci
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
||||||
def test_delete(api_client: TestClient, api_routes: AppRoutes, recipe_data: RecipeSiteTestCase, token):
|
def test_delete(api_client: TestClient, api_routes: AppRoutes, recipe_data: RecipeSiteTestCase, user_token):
|
||||||
recipe_url = api_routes.recipes_recipe_slug(recipe_data.expected_slug)
|
recipe_url = api_routes.recipes_recipe_slug(recipe_data.expected_slug)
|
||||||
response = api_client.delete(recipe_url, headers=token)
|
response = api_client.delete(recipe_url, headers=user_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
|
@ -10,8 +10,8 @@ def page_data():
|
||||||
return {"name": "My New Page", "position": 0, "categories": []}
|
return {"name": "My New Page", "position": 0, "categories": []}
|
||||||
|
|
||||||
|
|
||||||
def test_create_page(api_client: TestClient, api_routes: AppRoutes, token, page_data):
|
def test_create_page(api_client: TestClient, api_routes: AppRoutes, admin_token, page_data):
|
||||||
response = api_client.post(api_routes.site_settings_custom_pages, json=page_data, headers=token)
|
response = api_client.post(api_routes.site_settings_custom_pages, json=page_data, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
@ -25,16 +25,16 @@ def test_read_page(api_client: TestClient, api_routes: AppRoutes, page_data):
|
||||||
assert json.loads(response.text) == page_data
|
assert json.loads(response.text) == page_data
|
||||||
|
|
||||||
|
|
||||||
def test_update_page(api_client: TestClient, api_routes: AppRoutes, page_data, token):
|
def test_update_page(api_client: TestClient, api_routes: AppRoutes, page_data, admin_token):
|
||||||
page_data["id"] = 1
|
page_data["id"] = 1
|
||||||
page_data["name"] = "My New Name"
|
page_data["name"] = "My New Name"
|
||||||
response = api_client.put(api_routes.site_settings_custom_pages_id(1), json=page_data, headers=token)
|
response = api_client.put(api_routes.site_settings_custom_pages_id(1), json=page_data, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_delete_page(api_client: TestClient, api_routes: AppRoutes, token):
|
def test_delete_page(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
response = api_client.delete(api_routes.site_settings_custom_pages_id(1), headers=token)
|
response = api_client.delete(api_routes.site_settings_custom_pages_id(1), headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
|
@ -10,20 +10,20 @@ def group_data():
|
||||||
return {"name": "Test Group"}
|
return {"name": "Test Group"}
|
||||||
|
|
||||||
|
|
||||||
def test_create_group(api_client: TestClient, api_routes: AppRoutes, token):
|
def test_create_group(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
response = api_client.post(api_routes.groups, json={"name": "Test Group"}, headers=token)
|
response = api_client.post(api_routes.groups, json={"name": "Test Group"}, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
|
||||||
def test_get_self_group(api_client: TestClient, api_routes: AppRoutes, token):
|
def test_get_self_group(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
response = api_client.get(api_routes.groups, headers=token)
|
response = api_client.get(api_routes.groups, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert len(json.loads(response.text)) >= 2
|
assert len(json.loads(response.text)) >= 2
|
||||||
|
|
||||||
|
|
||||||
def test_update_group(api_client: TestClient, api_routes: AppRoutes, token):
|
def test_update_group(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
new_data = {
|
new_data = {
|
||||||
"name": "New Group Name",
|
"name": "New Group Name",
|
||||||
"id": 2,
|
"id": 2,
|
||||||
|
@ -36,23 +36,23 @@ def test_update_group(api_client: TestClient, api_routes: AppRoutes, token):
|
||||||
"shoppingLists": [],
|
"shoppingLists": [],
|
||||||
}
|
}
|
||||||
# Test Update
|
# Test Update
|
||||||
response = api_client.put(api_routes.groups_id(2), json=new_data, headers=token)
|
response = api_client.put(api_routes.groups_id(2), json=new_data, headers=admin_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
# Validate Changes
|
# Validate Changes
|
||||||
response = api_client.get(api_routes.groups, headers=token)
|
response = api_client.get(api_routes.groups, headers=admin_token)
|
||||||
all_groups = json.loads(response.text)
|
all_groups = json.loads(response.text)
|
||||||
id_2 = filter(lambda x: x["id"] == 2, all_groups)
|
id_2 = filter(lambda x: x["id"] == 2, all_groups)
|
||||||
assert next(id_2) == new_data
|
assert next(id_2) == new_data
|
||||||
|
|
||||||
|
|
||||||
def test_home_group_not_deletable(api_client: TestClient, api_routes: AppRoutes, token):
|
def test_home_group_not_deletable(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
response = api_client.delete(api_routes.groups_id(1), headers=token)
|
response = api_client.delete(api_routes.groups_id(1), headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
def test_delete_group(api_client: TestClient, api_routes: AppRoutes, token):
|
def test_delete_group(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
response = api_client.delete(api_routes.groups_id(2), headers=token)
|
response = api_client.delete(api_routes.groups_id(2), headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
|
@ -19,9 +19,9 @@ def backup_data():
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_import(api_client: TestClient, api_routes: AppRoutes, backup_data, token):
|
def test_import(api_client: TestClient, api_routes: AppRoutes, backup_data, admin_token):
|
||||||
import_route = api_routes.backups_file_name_import("test_backup_2021-Apr-27.zip")
|
import_route = api_routes.backups_file_name_import("test_backup_2021-Apr-27.zip")
|
||||||
response = api_client.post(import_route, json=backup_data, headers=token)
|
response = api_client.post(import_route, json=backup_data, headers=admin_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
for _, value in json.loads(response.content).items():
|
for _, value in json.loads(response.content).items():
|
||||||
for v in value:
|
for v in value:
|
||||||
|
|
|
@ -6,15 +6,15 @@ from tests.app_routes import AppRoutes
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
@fixture
|
||||||
def long_live_token(api_client: TestClient, api_routes: AppRoutes, token):
|
def long_live_token(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
response = api_client.post(api_routes.users_api_tokens, json={"name": "Test Fixture Token"}, headers=token)
|
response = api_client.post(api_routes.users_api_tokens, json={"name": "Test Fixture Token"}, headers=admin_token)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
return {"Authorization": f"Bearer {json.loads(response.text).get('token')}"}
|
return {"Authorization": f"Bearer {json.loads(response.text).get('token')}"}
|
||||||
|
|
||||||
|
|
||||||
def test_api_token_creation(api_client: TestClient, api_routes: AppRoutes, token):
|
def test_api_token_creation(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
response = api_client.post(api_routes.users_api_tokens, json={"name": "Test API Token"}, headers=token)
|
response = api_client.post(api_routes.users_api_tokens, json={"name": "Test API Token"}, headers=admin_token)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,9 +24,9 @@ def test_use_token(api_client: TestClient, api_routes: AppRoutes, long_live_toke
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_delete_token(api_client: TestClient, api_routes: AppRoutes, token):
|
def test_delete_token(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
response = api_client.delete(api_routes.users_api_tokens_token_id(1), headers=token)
|
response = api_client.delete(api_routes.users_api_tokens_token_id(1), headers=admin_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
response = api_client.delete(api_routes.users_api_tokens_token_id(2), headers=token)
|
response = api_client.delete(api_routes.users_api_tokens_token_id(2), headers=admin_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
|
@ -25,8 +25,8 @@ def get_meal_plan_template(first=None, second=None):
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def slug_1(api_client: TestClient, api_routes: AppRoutes, token, recipe_store: list[RecipeSiteTestCase]):
|
def slug_1(api_client: TestClient, api_routes: AppRoutes, admin_token, recipe_store: list[RecipeSiteTestCase]):
|
||||||
slug_1 = api_client.post(api_routes.recipes_create_url, json={"url": recipe_store[0].url}, headers=token)
|
slug_1 = api_client.post(api_routes.recipes_create_url, json={"url": recipe_store[0].url}, headers=admin_token)
|
||||||
slug_1 = json.loads(slug_1.content)
|
slug_1 = json.loads(slug_1.content)
|
||||||
|
|
||||||
yield slug_1
|
yield slug_1
|
||||||
|
@ -35,8 +35,8 @@ def slug_1(api_client: TestClient, api_routes: AppRoutes, token, recipe_store: l
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def slug_2(api_client: TestClient, api_routes: AppRoutes, token, recipe_store: list[RecipeSiteTestCase]):
|
def slug_2(api_client: TestClient, api_routes: AppRoutes, admin_token, recipe_store: list[RecipeSiteTestCase]):
|
||||||
slug_2 = api_client.post(api_routes.recipes_create_url, json={"url": recipe_store[1].url}, headers=token)
|
slug_2 = api_client.post(api_routes.recipes_create_url, json={"url": recipe_store[1].url}, headers=admin_token)
|
||||||
slug_2 = json.loads(slug_2.content)
|
slug_2 = json.loads(slug_2.content)
|
||||||
|
|
||||||
yield slug_2
|
yield slug_2
|
||||||
|
@ -44,15 +44,15 @@ def slug_2(api_client: TestClient, api_routes: AppRoutes, token, recipe_store: l
|
||||||
api_client.delete(api_routes.recipes_recipe_slug(slug_2))
|
api_client.delete(api_routes.recipes_recipe_slug(slug_2))
|
||||||
|
|
||||||
|
|
||||||
def test_create_mealplan(api_client: TestClient, api_routes: AppRoutes, slug_1, slug_2, token):
|
def test_create_mealplan(api_client: TestClient, api_routes: AppRoutes, slug_1, slug_2, admin_token):
|
||||||
meal_plan = get_meal_plan_template(slug_1, slug_2)
|
meal_plan = get_meal_plan_template(slug_1, slug_2)
|
||||||
|
|
||||||
response = api_client.post(api_routes.meal_plans_create, json=meal_plan, headers=token)
|
response = api_client.post(api_routes.meal_plans_create, json=meal_plan, headers=admin_token)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
|
||||||
def test_read_mealplan(api_client: TestClient, api_routes: AppRoutes, slug_1, slug_2, token):
|
def test_read_mealplan(api_client: TestClient, api_routes: AppRoutes, slug_1, slug_2, admin_token):
|
||||||
response = api_client.get(api_routes.meal_plans_all, headers=token)
|
response = api_client.get(api_routes.meal_plans_all, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
@ -65,9 +65,9 @@ def test_read_mealplan(api_client: TestClient, api_routes: AppRoutes, slug_1, sl
|
||||||
assert meals[1]["meals"][0]["slug"] == meal_plan_template["planDays"][1]["meals"][0]["slug"]
|
assert meals[1]["meals"][0]["slug"] == meal_plan_template["planDays"][1]["meals"][0]["slug"]
|
||||||
|
|
||||||
|
|
||||||
def test_update_mealplan(api_client: TestClient, api_routes: AppRoutes, slug_1, slug_2, token):
|
def test_update_mealplan(api_client: TestClient, api_routes: AppRoutes, slug_1, slug_2, admin_token):
|
||||||
|
|
||||||
response = api_client.get(api_routes.meal_plans_all, headers=token)
|
response = api_client.get(api_routes.meal_plans_all, headers=admin_token)
|
||||||
|
|
||||||
existing_mealplan = json.loads(response.text)
|
existing_mealplan = json.loads(response.text)
|
||||||
existing_mealplan = existing_mealplan[0]
|
existing_mealplan = existing_mealplan[0]
|
||||||
|
@ -77,11 +77,11 @@ def test_update_mealplan(api_client: TestClient, api_routes: AppRoutes, slug_1,
|
||||||
existing_mealplan["planDays"][0]["meals"][0]["slug"] = slug_2
|
existing_mealplan["planDays"][0]["meals"][0]["slug"] = slug_2
|
||||||
existing_mealplan["planDays"][1]["meals"][0]["slug"] = slug_1
|
existing_mealplan["planDays"][1]["meals"][0]["slug"] = slug_1
|
||||||
|
|
||||||
response = api_client.put(api_routes.meal_plans_plan_id(plan_uid), json=existing_mealplan, headers=token)
|
response = api_client.put(api_routes.meal_plans_plan_id(plan_uid), json=existing_mealplan, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
response = api_client.get(api_routes.meal_plans_all, headers=token)
|
response = api_client.get(api_routes.meal_plans_all, headers=admin_token)
|
||||||
existing_mealplan = json.loads(response.text)
|
existing_mealplan = json.loads(response.text)
|
||||||
existing_mealplan = existing_mealplan[0]
|
existing_mealplan = existing_mealplan[0]
|
||||||
|
|
||||||
|
@ -89,14 +89,14 @@ def test_update_mealplan(api_client: TestClient, api_routes: AppRoutes, slug_1,
|
||||||
assert existing_mealplan["planDays"][1]["meals"][0]["slug"] == slug_1
|
assert existing_mealplan["planDays"][1]["meals"][0]["slug"] == slug_1
|
||||||
|
|
||||||
|
|
||||||
def test_delete_mealplan(api_client: TestClient, api_routes: AppRoutes, token):
|
def test_delete_mealplan(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
response = api_client.get(api_routes.meal_plans_all, headers=token)
|
response = api_client.get(api_routes.meal_plans_all, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
existing_mealplan = json.loads(response.text)
|
existing_mealplan = json.loads(response.text)
|
||||||
existing_mealplan = existing_mealplan[0]
|
existing_mealplan = existing_mealplan[0]
|
||||||
|
|
||||||
plan_uid = existing_mealplan.get("uid")
|
plan_uid = existing_mealplan.get("uid")
|
||||||
response = api_client.delete(api_routes.meal_plans_plan_id(plan_uid), headers=token)
|
response = api_client.delete(api_routes.meal_plans_plan_id(plan_uid), headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
|
@ -22,22 +22,22 @@ def chowdown_zip():
|
||||||
zip_copy.unlink()
|
zip_copy.unlink()
|
||||||
|
|
||||||
|
|
||||||
def test_upload_chowdown_zip(api_client: TestClient, api_routes: AppRoutes, chowdown_zip: Path, token):
|
def test_upload_chowdown_zip(api_client: TestClient, api_routes: AppRoutes, chowdown_zip: Path, admin_token):
|
||||||
upload_url = api_routes.migrations_import_type_upload("chowdown")
|
upload_url = api_routes.migrations_import_type_upload("chowdown")
|
||||||
response = api_client.post(upload_url, files={"archive": chowdown_zip.open("rb")}, headers=token)
|
response = api_client.post(upload_url, files={"archive": chowdown_zip.open("rb")}, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
assert app_dirs.MIGRATION_DIR.joinpath("chowdown", chowdown_zip.name).is_file()
|
assert app_dirs.MIGRATION_DIR.joinpath("chowdown", chowdown_zip.name).is_file()
|
||||||
|
|
||||||
|
|
||||||
def test_import_chowdown_directory(api_client: TestClient, api_routes: AppRoutes, chowdown_zip: Path, token):
|
def test_import_chowdown_directory(api_client: TestClient, api_routes: AppRoutes, chowdown_zip: Path, admin_token):
|
||||||
delete_url = api_routes.recipes_recipe_slug("roasted-okra")
|
delete_url = api_routes.recipes_recipe_slug("roasted-okra")
|
||||||
api_client.delete(delete_url, headers=token) # TODO: Manage Test Data better
|
api_client.delete(delete_url, headers=admin_token) # TODO: Manage Test Data better
|
||||||
selection = chowdown_zip.name
|
selection = chowdown_zip.name
|
||||||
|
|
||||||
import_url = api_routes.migrations_import_type_file_name_import("chowdown", selection)
|
import_url = api_routes.migrations_import_type_file_name_import("chowdown", selection)
|
||||||
response = api_client.post(import_url, headers=token)
|
response = api_client.post(import_url, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
@ -47,10 +47,10 @@ def test_import_chowdown_directory(api_client: TestClient, api_routes: AppRoutes
|
||||||
assert report.get("status") is True
|
assert report.get("status") is True
|
||||||
|
|
||||||
|
|
||||||
def test_delete_chowdown_migration_data(api_client: TestClient, api_routes: AppRoutes, chowdown_zip: Path, token):
|
def test_delete_chowdown_migration_data(api_client: TestClient, api_routes: AppRoutes, chowdown_zip: Path, admin_token):
|
||||||
selection = chowdown_zip.name
|
selection = chowdown_zip.name
|
||||||
delete_url = api_routes.migrations_import_type_file_name_delete("chowdown", selection)
|
delete_url = api_routes.migrations_import_type_file_name_delete("chowdown", selection)
|
||||||
response = api_client.delete(delete_url, headers=token)
|
response = api_client.delete(delete_url, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert not app_dirs.MIGRATION_DIR.joinpath(chowdown_zip.name).is_file()
|
assert not app_dirs.MIGRATION_DIR.joinpath(chowdown_zip.name).is_file()
|
||||||
|
@ -70,19 +70,19 @@ def nextcloud_zip():
|
||||||
zip_copy.unlink()
|
zip_copy.unlink()
|
||||||
|
|
||||||
|
|
||||||
def test_upload_nextcloud_zip(api_client: TestClient, api_routes: AppRoutes, nextcloud_zip, token):
|
def test_upload_nextcloud_zip(api_client: TestClient, api_routes: AppRoutes, nextcloud_zip, admin_token):
|
||||||
upload_url = api_routes.migrations_import_type_upload("nextcloud")
|
upload_url = api_routes.migrations_import_type_upload("nextcloud")
|
||||||
response = api_client.post(upload_url, files={"archive": nextcloud_zip.open("rb")}, headers=token)
|
response = api_client.post(upload_url, files={"archive": nextcloud_zip.open("rb")}, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
assert app_dirs.MIGRATION_DIR.joinpath("nextcloud", nextcloud_zip.name).is_file()
|
assert app_dirs.MIGRATION_DIR.joinpath("nextcloud", nextcloud_zip.name).is_file()
|
||||||
|
|
||||||
|
|
||||||
def test_import_nextcloud_directory(api_client: TestClient, api_routes: AppRoutes, nextcloud_zip, token):
|
def test_import_nextcloud_directory(api_client: TestClient, api_routes: AppRoutes, nextcloud_zip, admin_token):
|
||||||
selection = nextcloud_zip.name
|
selection = nextcloud_zip.name
|
||||||
import_url = api_routes.migrations_import_type_file_name_import("nextcloud", selection)
|
import_url = api_routes.migrations_import_type_file_name_import("nextcloud", selection)
|
||||||
response = api_client.post(import_url, headers=token)
|
response = api_client.post(import_url, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
@ -91,10 +91,12 @@ def test_import_nextcloud_directory(api_client: TestClient, api_routes: AppRoute
|
||||||
assert report.get("status") is True
|
assert report.get("status") is True
|
||||||
|
|
||||||
|
|
||||||
def test_delete__nextcloud_migration_data(api_client: TestClient, api_routes: AppRoutes, nextcloud_zip: Path, token):
|
def test_delete__nextcloud_migration_data(
|
||||||
|
api_client: TestClient, api_routes: AppRoutes, nextcloud_zip: Path, admin_token
|
||||||
|
):
|
||||||
selection = nextcloud_zip.name
|
selection = nextcloud_zip.name
|
||||||
delete_url = api_routes.migrations_import_type_file_name_delete("nextcloud", selection)
|
delete_url = api_routes.migrations_import_type_file_name_delete("nextcloud", selection)
|
||||||
response = api_client.delete(delete_url, headers=token)
|
response = api_client.delete(delete_url, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert not app_dirs.MIGRATION_DIR.joinpath(nextcloud_zip.name).is_file()
|
assert not app_dirs.MIGRATION_DIR.joinpath(nextcloud_zip.name).is_file()
|
||||||
|
|
|
@ -19,11 +19,11 @@ def test_default_settings(api_client: TestClient, api_routes: AppRoutes, default
|
||||||
assert json.loads(response.content) == default_settings
|
assert json.loads(response.content) == default_settings
|
||||||
|
|
||||||
|
|
||||||
def test_update_settings(api_client: TestClient, api_routes: AppRoutes, default_settings, token):
|
def test_update_settings(api_client: TestClient, api_routes: AppRoutes, default_settings, admin_token):
|
||||||
default_settings["language"] = "fr"
|
default_settings["language"] = "fr"
|
||||||
default_settings["showRecent"] = False
|
default_settings["showRecent"] = False
|
||||||
|
|
||||||
response = api_client.put(api_routes.site_settings, json=default_settings, headers=token)
|
response = api_client.put(api_routes.site_settings, json=default_settings, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,10 @@ from tests.app_routes import AppRoutes
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def active_link(api_client: TestClient, api_routes: AppRoutes, token):
|
def active_link(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
data = {"name": "Fixture Token", "admin": True}
|
data = {"name": "Fixture Token", "admin": True}
|
||||||
|
|
||||||
response = api_client.post(api_routes.users_sign_ups, json=data, headers=token)
|
response = api_client.post(api_routes.users_sign_ups, json=data, headers=admin_token)
|
||||||
|
|
||||||
return SignUpToken(**json.loads(response.text))
|
return SignUpToken(**json.loads(response.text))
|
||||||
|
|
||||||
|
@ -26,10 +26,10 @@ def sign_up_user():
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_create_sign_up_link(api_client: TestClient, api_routes: AppRoutes, token):
|
def test_create_sign_up_link(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
data = {"name": "Test Token", "admin": False}
|
data = {"name": "Test Token", "admin": False}
|
||||||
|
|
||||||
response = api_client.post(api_routes.users_sign_ups, json=data, headers=token)
|
response = api_client.post(api_routes.users_sign_ups, json=data, headers=admin_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,11 +47,11 @@ def test_new_user_signup(api_client: TestClient, api_routes: AppRoutes, active_l
|
||||||
|
|
||||||
|
|
||||||
def test_delete_sign_up_link(
|
def test_delete_sign_up_link(
|
||||||
api_client: TestClient, api_routes: AppRoutes, token, active_link: SignUpToken, sign_up_user
|
api_client: TestClient, api_routes: AppRoutes, admin_token, active_link: SignUpToken, sign_up_user
|
||||||
):
|
):
|
||||||
response = api_client.delete(api_routes.users_sign_ups_token(active_link.token), headers=token)
|
response = api_client.delete(api_routes.users_sign_ups_token(active_link.token), headers=admin_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
# Validate Token is Gone
|
# Validate admin_token is Gone
|
||||||
response = api_client.get(api_routes.users_sign_ups, headers=token)
|
response = api_client.get(api_routes.users_sign_ups, headers=admin_token)
|
||||||
assert sign_up_user not in json.loads(response.content)
|
assert sign_up_user not in json.loads(response.content)
|
||||||
|
|
|
@ -28,38 +28,38 @@ def new_theme():
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_default_theme(api_client: TestClient, api_routes: AppRoutes, default_theme):
|
def test_default_theme(api_client: TestClient, api_routes: AppRoutes, default_theme, user_token):
|
||||||
response = api_client.get(api_routes.themes_id(1))
|
response = api_client.get(api_routes.themes_id(1), headers=user_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert json.loads(response.content) == default_theme
|
assert json.loads(response.content) == default_theme
|
||||||
|
|
||||||
|
|
||||||
def test_create_theme(api_client: TestClient, api_routes: AppRoutes, new_theme, token):
|
def test_create_theme(api_client: TestClient, api_routes: AppRoutes, new_theme, user_token):
|
||||||
|
|
||||||
response = api_client.post(api_routes.themes_create, json=new_theme, headers=token)
|
response = api_client.post(api_routes.themes_create, json=new_theme, headers=user_token)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
response = api_client.get(api_routes.themes_id(new_theme.get("id")), headers=token)
|
response = api_client.get(api_routes.themes_id(new_theme.get("id")), headers=user_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert json.loads(response.content) == new_theme
|
assert json.loads(response.content) == new_theme
|
||||||
|
|
||||||
|
|
||||||
def test_read_all_themes(api_client: TestClient, api_routes: AppRoutes, default_theme, new_theme):
|
def test_read_all_themes(api_client: TestClient, api_routes: AppRoutes, default_theme, new_theme, user_token):
|
||||||
response = api_client.get(api_routes.themes)
|
response = api_client.get(api_routes.themes, headers=user_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
response_dict = json.loads(response.content)
|
response_dict = json.loads(response.content)
|
||||||
assert default_theme in response_dict
|
assert default_theme in response_dict
|
||||||
assert new_theme in response_dict
|
assert new_theme in response_dict
|
||||||
|
|
||||||
|
|
||||||
def test_read_theme(api_client: TestClient, api_routes: AppRoutes, default_theme, new_theme):
|
def test_read_theme(api_client: TestClient, api_routes: AppRoutes, default_theme, new_theme, user_token):
|
||||||
for theme in [default_theme, new_theme]:
|
for theme in [default_theme, new_theme]:
|
||||||
response = api_client.get(api_routes.themes_id(theme.get("id")))
|
response = api_client.get(api_routes.themes_id(theme.get("id")), headers=user_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert json.loads(response.content) == theme
|
assert json.loads(response.content) == theme
|
||||||
|
|
||||||
|
|
||||||
def test_update_theme(api_client: TestClient, api_routes: AppRoutes, token, new_theme):
|
def test_update_theme(api_client: TestClient, api_routes: AppRoutes, user_token, new_theme):
|
||||||
theme_colors = {
|
theme_colors = {
|
||||||
"primary": "#E12345",
|
"primary": "#E12345",
|
||||||
"accent": "#012345",
|
"accent": "#012345",
|
||||||
|
@ -72,14 +72,14 @@ def test_update_theme(api_client: TestClient, api_routes: AppRoutes, token, new_
|
||||||
|
|
||||||
new_theme["colors"] = theme_colors
|
new_theme["colors"] = theme_colors
|
||||||
new_theme["name"] = "New Theme Name"
|
new_theme["name"] = "New Theme Name"
|
||||||
response = api_client.put(api_routes.themes_id(new_theme.get("id")), json=new_theme, headers=token)
|
response = api_client.put(api_routes.themes_id(new_theme.get("id")), json=new_theme, headers=user_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
response = api_client.get(api_routes.themes_id(new_theme.get("id")))
|
response = api_client.get(api_routes.themes_id(new_theme.get("id")), headers=user_token)
|
||||||
assert json.loads(response.content) == new_theme
|
assert json.loads(response.content) == new_theme
|
||||||
|
|
||||||
|
|
||||||
def test_delete_theme(api_client: TestClient, api_routes: AppRoutes, default_theme, new_theme, token):
|
def test_delete_theme(api_client: TestClient, api_routes: AppRoutes, default_theme, new_theme, user_token):
|
||||||
for theme in [default_theme, new_theme]:
|
for theme in [default_theme, new_theme]:
|
||||||
response = api_client.delete(api_routes.themes_id(theme.get("id")), headers=token)
|
response = api_client.delete(api_routes.themes_id(theme.get("id")), headers=user_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
|
@ -9,7 +9,7 @@ from tests.app_routes import AppRoutes
|
||||||
|
|
||||||
|
|
||||||
@fixture(scope="session")
|
@fixture(scope="session")
|
||||||
def default_user():
|
def admin_user():
|
||||||
return UserOut(
|
return UserOut(
|
||||||
id=1,
|
id=1,
|
||||||
fullName="Change Me",
|
fullName="Change Me",
|
||||||
|
@ -24,7 +24,7 @@ def default_user():
|
||||||
@fixture(scope="session")
|
@fixture(scope="session")
|
||||||
def new_user():
|
def new_user():
|
||||||
return UserOut(
|
return UserOut(
|
||||||
id=3,
|
id=4,
|
||||||
fullName="My New User",
|
fullName="My New User",
|
||||||
username="My New User",
|
username="My New User",
|
||||||
email="newuser@email.com",
|
email="newuser@email.com",
|
||||||
|
@ -41,27 +41,27 @@ def test_failed_login(api_client: TestClient, api_routes: AppRoutes):
|
||||||
assert response.status_code == 401
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
def test_superuser_login(api_client: TestClient, api_routes: AppRoutes, token):
|
def test_superuser_login(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
form_data = {"username": "changeme@email.com", "password": "MyPassword"}
|
form_data = {"username": "changeme@email.com", "password": "MyPassword"}
|
||||||
response = api_client.post(api_routes.auth_token, form_data)
|
response = api_client.post(api_routes.auth_token, form_data)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
new_token = json.loads(response.text).get("access_token")
|
new_token = json.loads(response.text).get("access_token")
|
||||||
|
|
||||||
response = api_client.get(api_routes.users_self, headers=token)
|
response = api_client.get(api_routes.users_self, headers=admin_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
return {"Authorization": f"Bearer {new_token}"}
|
return {"Authorization": f"Bearer {new_token}"}
|
||||||
|
|
||||||
|
|
||||||
def test_init_superuser(api_client: TestClient, api_routes: AppRoutes, token, default_user: UserOut):
|
def test_init_superuser(api_client: TestClient, api_routes: AppRoutes, admin_token, admin_user: UserOut):
|
||||||
response = api_client.get(api_routes.users_id(1), headers=token)
|
response = api_client.get(api_routes.users_id(1), headers=admin_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
assert json.loads(response.text) == default_user.dict(by_alias=True)
|
assert json.loads(response.text) == admin_user.dict(by_alias=True)
|
||||||
|
|
||||||
|
|
||||||
def test_create_user(api_client: TestClient, api_routes: AppRoutes, token, new_user):
|
def test_create_user(api_client: TestClient, api_routes: AppRoutes, admin_token, new_user):
|
||||||
create_data = {
|
create_data = {
|
||||||
"fullName": "My New User",
|
"fullName": "My New User",
|
||||||
"email": "newuser@email.com",
|
"email": "newuser@email.com",
|
||||||
|
@ -71,32 +71,74 @@ def test_create_user(api_client: TestClient, api_routes: AppRoutes, token, new_u
|
||||||
"tokens": [],
|
"tokens": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
response = api_client.post(api_routes.users, json=create_data, headers=token)
|
response = api_client.post(api_routes.users, json=create_data, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
assert json.loads(response.text) == new_user.dict(by_alias=True)
|
assert json.loads(response.text) == new_user.dict(by_alias=True)
|
||||||
assert True
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_all_users(api_client: TestClient, api_routes: AppRoutes, token, new_user, default_user):
|
def test_create_user_as_non_admin(api_client: TestClient, api_routes: AppRoutes, user_token):
|
||||||
response = api_client.get(api_routes.users, headers=token)
|
create_data = {
|
||||||
|
"fullName": "My New User",
|
||||||
|
"email": "newuser@email.com",
|
||||||
|
"password": "MyStrongPassword",
|
||||||
|
"group": "Home",
|
||||||
|
"admin": False,
|
||||||
|
"tokens": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
response = api_client.post(api_routes.users, json=create_data, headers=user_token)
|
||||||
|
|
||||||
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_all_users(api_client: TestClient, api_routes: AppRoutes, admin_token, new_user, admin_user):
|
||||||
|
response = api_client.get(api_routes.users, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
all_users = json.loads(response.text)
|
all_users = json.loads(response.text)
|
||||||
assert default_user.dict(by_alias=True) in all_users
|
assert admin_user.dict(by_alias=True) in all_users
|
||||||
assert new_user.dict(by_alias=True) in all_users
|
assert new_user.dict(by_alias=True) in all_users
|
||||||
|
|
||||||
|
|
||||||
def test_update_user(api_client: TestClient, api_routes: AppRoutes, token):
|
def test_update_user(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
update_data = {"id": 1, "fullName": "Updated Name", "email": "changeme@email.com", "group": "Home", "admin": True}
|
update_data = {"id": 1, "fullName": "Updated Name", "email": "changeme@email.com", "group": "Home", "admin": True}
|
||||||
response = api_client.put(api_routes.users_id(1), headers=token, json=update_data)
|
response = api_client.put(api_routes.users_id(1), headers=admin_token, json=update_data)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert json.loads(response.text).get("access_token")
|
assert json.loads(response.text).get("access_token")
|
||||||
|
|
||||||
|
|
||||||
def test_reset_user_password(api_client: TestClient, api_routes: AppRoutes, token):
|
def test_update_other_user_as_not_admin(api_client: TestClient, api_routes: AppRoutes, user_token):
|
||||||
response = api_client.put(api_routes.users_id_reset_password(3), headers=token)
|
update_data = {"id": 1, "fullName": "Updated Name", "email": "changeme@email.com", "group": "Home", "admin": True}
|
||||||
|
response = api_client.put(api_routes.users_id(1), headers=user_token, json=update_data)
|
||||||
|
|
||||||
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_self_as_not_admin(api_client: TestClient, api_routes: AppRoutes, user_token):
|
||||||
|
update_data = {"id": 3, "fullName": "User fullname", "email": "user@email.com", "group": "Home", "admin": False}
|
||||||
|
response = api_client.put(api_routes.users_id(3), headers=user_token, json=update_data)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_self_demote_admin(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
|
update_data = {"id": 1, "fullName": "Updated Name", "email": "changeme@email.com", "group": "Home", "admin": False}
|
||||||
|
response = api_client.put(api_routes.users_id(1), headers=admin_token, json=update_data)
|
||||||
|
|
||||||
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
def test_self_promote_admin(api_client: TestClient, api_routes: AppRoutes, user_token):
|
||||||
|
update_data = {"id": 3, "fullName": "Updated Name", "email": "user@email.com", "group": "Home", "admin": True}
|
||||||
|
response = api_client.put(api_routes.users_id(3), headers=user_token, json=update_data)
|
||||||
|
|
||||||
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
def test_reset_user_password(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
|
response = api_client.put(api_routes.users_id_reset_password(4), headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
@ -106,23 +148,23 @@ def test_reset_user_password(api_client: TestClient, api_routes: AppRoutes, toke
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_delete_user(api_client: TestClient, api_routes: AppRoutes, token):
|
def test_delete_user(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
response = api_client.delete(api_routes.users_id(2), headers=token)
|
response = api_client.delete(api_routes.users_id(2), headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_update_user_image(
|
def test_update_user_image(
|
||||||
api_client: TestClient, api_routes: AppRoutes, test_image_jpg: Path, test_image_png: Path, token
|
api_client: TestClient, api_routes: AppRoutes, test_image_jpg: Path, test_image_png: Path, admin_token
|
||||||
):
|
):
|
||||||
response = api_client.post(
|
response = api_client.post(
|
||||||
api_routes.users_id_image(2), files={"profile_image": test_image_jpg.open("rb")}, headers=token
|
api_routes.users_id_image(2), files={"profile_image": test_image_jpg.open("rb")}, headers=admin_token
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
response = api_client.post(
|
response = api_client.post(
|
||||||
api_routes.users_id_image(2), files={"profile_image": test_image_png.open("rb")}, headers=token
|
api_routes.users_id_image(2), files={"profile_image": test_image_png.open("rb")}, headers=admin_token
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue