mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 10:13:32 -04:00 
			
		
		
		
	Added validators for users and recipes (#1052)
* Added validators for users and recipes provide a simple get api, allowing to test for existence of - user by username - recipe by slug - group by name (not tested yet) * updated formatting * Use group_id+slug for recipes, use ValidationRespone * Fixed Flake8 errors and warnings * add missing field for TestUser init
This commit is contained in:
		| @@ -1,3 +1,5 @@ | |||||||
|  | from typing import Union | ||||||
|  |  | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
|  |  | ||||||
| from mealie.db.models.group import Group | from mealie.db.models.group import Group | ||||||
| @@ -22,3 +24,9 @@ class RepositoryGroup(RepositoryGeneric[GroupInDB, Group]): | |||||||
|         group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none() |         group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none() | ||||||
|  |  | ||||||
|         return group.mealplans |         return group.mealplans | ||||||
|  |  | ||||||
|  |     def get_by_name(self, name: str, limit=1) -> Union[GroupInDB, Group, None]: | ||||||
|  |         dbgroup = self.session.query(self.sql_model).filter_by(**{"name": name}).one_or_none() | ||||||
|  |         if dbgroup is None: | ||||||
|  |             return None | ||||||
|  |         return self.schema.from_orm(dbgroup) | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| from random import randint | from random import randint | ||||||
| from typing import Any | from typing import Any, Optional | ||||||
| from uuid import UUID | from uuid import UUID | ||||||
|  |  | ||||||
| from sqlalchemy import and_, func | from sqlalchemy import and_, func | ||||||
| @@ -143,3 +143,13 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]): | |||||||
|             .order_by(func.random())  # Postgres and SQLite specific |             .order_by(func.random())  # Postgres and SQLite specific | ||||||
|             .limit(limit) |             .limit(limit) | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |     def get_by_slug(self, group_id: UUID, slug: str, limit=1) -> Optional[Recipe]: | ||||||
|  |         dbrecipe = ( | ||||||
|  |             self.session.query(RecipeModel) | ||||||
|  |             .filter(RecipeModel.group_id == group_id, RecipeModel.slug == slug) | ||||||
|  |             .one_or_none() | ||||||
|  |         ) | ||||||
|  |         if dbrecipe is None: | ||||||
|  |             return None | ||||||
|  |         return self.schema.from_orm(dbrecipe) | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import random | import random | ||||||
| import shutil | import shutil | ||||||
|  | from typing import Optional | ||||||
|  |  | ||||||
| from mealie.assets import users as users_assets | from mealie.assets import users as users_assets | ||||||
| from mealie.schema.user.user import PrivateUser, User | from mealie.schema.user.user import PrivateUser, User | ||||||
| @@ -34,3 +35,9 @@ class RepositoryUsers(RepositoryGeneric[PrivateUser, User]): | |||||||
|         # Delete the user's directory |         # Delete the user's directory | ||||||
|         shutil.rmtree(PrivateUser.get_directory(id)) |         shutil.rmtree(PrivateUser.get_directory(id)) | ||||||
|         return entry |         return entry | ||||||
|  |  | ||||||
|  |     def get_by_username(self, username: str, limit=1) -> Optional[User]: | ||||||
|  |         dbuser = self.session.query(User).filter(User.username == username).one_or_none() | ||||||
|  |         if dbuser is None: | ||||||
|  |             return None | ||||||
|  |         return self.schema.from_orm(dbuser) | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| from fastapi import APIRouter | from fastapi import APIRouter | ||||||
|  |  | ||||||
| from . import admin, app, auth, comments, groups, organizers, parser, recipe, shared, unit_and_foods, users | from . import admin, app, auth, comments, groups, organizers, parser, recipe, shared, unit_and_foods, users, validators | ||||||
|  |  | ||||||
| router = APIRouter(prefix="/api") | router = APIRouter(prefix="/api") | ||||||
|  |  | ||||||
| @@ -15,3 +15,4 @@ router.include_router(comments.router) | |||||||
| router.include_router(parser.router) | router.include_router(parser.router) | ||||||
| router.include_router(unit_and_foods.router) | router.include_router(unit_and_foods.router) | ||||||
| router.include_router(admin.router) | router.include_router(admin.router) | ||||||
|  | router.include_router(validators.router) | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								mealie/routes/validators/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								mealie/routes/validators/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | from fastapi import APIRouter | ||||||
|  |  | ||||||
|  | from . import validators | ||||||
|  |  | ||||||
|  | prefix = "/validators" | ||||||
|  |  | ||||||
|  | router = APIRouter() | ||||||
|  |  | ||||||
|  | router.include_router(validators.router, prefix=prefix, tags=["Validators"]) | ||||||
							
								
								
									
										34
									
								
								mealie/routes/validators/validators.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								mealie/routes/validators/validators.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | from uuid import UUID | ||||||
|  |  | ||||||
|  | from fastapi import APIRouter, Depends | ||||||
|  | from sqlalchemy.orm.session import Session | ||||||
|  |  | ||||||
|  | from mealie.db.db_setup import generate_session | ||||||
|  | from mealie.repos.all_repositories import get_repositories | ||||||
|  | from mealie.schema.response import ValidationResponse | ||||||
|  |  | ||||||
|  | router = APIRouter() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @router.get("/user/{name}", response_model=ValidationResponse) | ||||||
|  | def validate_user(name: str, session: Session = Depends(generate_session)): | ||||||
|  |     """Checks if a user with the given name exists""" | ||||||
|  |     db = get_repositories(session) | ||||||
|  |     existing_element = db.users.get_by_username(name) | ||||||
|  |     return ValidationResponse(valid=existing_element is None) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @router.get("/group/{name}", response_model=ValidationResponse) | ||||||
|  | def validate_group(name: str, session: Session = Depends(generate_session)): | ||||||
|  |     """Checks if a group with the given name exists""" | ||||||
|  |     db = get_repositories(session) | ||||||
|  |     existing_element = db.groups.get_by_name(name) | ||||||
|  |     return ValidationResponse(valid=existing_element is None) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @router.get("/recipe/{group_id}/{slug}", response_model=ValidationResponse) | ||||||
|  | def validate_recipe(group_id: UUID, slug: str, session: Session = Depends(generate_session)): | ||||||
|  |     """Checks if a group with the given slug exists""" | ||||||
|  |     db = get_repositories(session) | ||||||
|  |     existing_element = db.recipes.get_by_slug(group_id, slug) | ||||||
|  |     return ValidationResponse(valid=existing_element is None) | ||||||
| @@ -1,2 +1,3 @@ | |||||||
| # GENERATED CODE - DO NOT MODIFY BY HAND | # GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
| from .responses import * | from .responses import * | ||||||
|  | from .validation import * | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								mealie/schema/response/validation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								mealie/schema/response/validation.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | from pydantic import BaseModel | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ValidationResponse(BaseModel): | ||||||
|  |     valid: bool = False | ||||||
							
								
								
									
										1
									
								
								tests/fixtures/fixture_admin.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								tests/fixtures/fixture_admin.py
									
									
									
									
										vendored
									
									
								
							| @@ -33,6 +33,7 @@ def admin_user(api_client: TestClient, api_routes: utils.AppRoutes): | |||||||
|         yield utils.TestUser( |         yield utils.TestUser( | ||||||
|             _group_id=user_data.get("groupId"), |             _group_id=user_data.get("groupId"), | ||||||
|             user_id=user_data.get("id"), |             user_id=user_data.get("id"), | ||||||
|  |             username=user_data.get("username"), | ||||||
|             email=user_data.get("email"), |             email=user_data.get("email"), | ||||||
|             token=token, |             token=token, | ||||||
|         ) |         ) | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								tests/fixtures/fixture_users.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								tests/fixtures/fixture_users.py
									
									
									
									
										vendored
									
									
								
							| @@ -28,6 +28,7 @@ def build_unique_user(group: str, api_client: requests) -> utils.TestUser: | |||||||
|         _group_id=user_data.get("groupId"), |         _group_id=user_data.get("groupId"), | ||||||
|         user_id=user_data.get("id"), |         user_id=user_data.get("id"), | ||||||
|         email=user_data.get("email"), |         email=user_data.get("email"), | ||||||
|  |         username=user_data.get("username"), | ||||||
|         token=token, |         token=token, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
| @@ -69,6 +70,7 @@ def g2_user(admin_token, api_client: TestClient, api_routes: utils.AppRoutes): | |||||||
|             _group_id=group_id, |             _group_id=group_id, | ||||||
|             token=token, |             token=token, | ||||||
|             email=create_data["email"], |             email=create_data["email"], | ||||||
|  |             username=create_data.get("username"), | ||||||
|         ) |         ) | ||||||
|     finally: |     finally: | ||||||
|         # TODO: Delete User after test |         # TODO: Delete User after test | ||||||
| @@ -93,6 +95,7 @@ def unique_user(api_client: TestClient, api_routes: utils.AppRoutes): | |||||||
|             _group_id=user_data.get("groupId"), |             _group_id=user_data.get("groupId"), | ||||||
|             user_id=user_data.get("id"), |             user_id=user_data.get("id"), | ||||||
|             email=user_data.get("email"), |             email=user_data.get("email"), | ||||||
|  |             username=user_data.get("username"), | ||||||
|             token=token, |             token=token, | ||||||
|         ) |         ) | ||||||
|     finally: |     finally: | ||||||
| @@ -142,6 +145,7 @@ def user_tuple(admin_token, api_client: requests, api_routes: utils.AppRoutes) - | |||||||
|             utils.TestUser( |             utils.TestUser( | ||||||
|                 _group_id=user_data.get("groupId"), |                 _group_id=user_data.get("groupId"), | ||||||
|                 user_id=user_data.get("id"), |                 user_id=user_data.get("id"), | ||||||
|  |                 username=user_data.get("username"), | ||||||
|                 email=user_data.get("email"), |                 email=user_data.get("email"), | ||||||
|                 token=token, |                 token=token, | ||||||
|             ) |             ) | ||||||
|   | |||||||
							
								
								
									
										46
									
								
								tests/integration_tests/test_validators.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								tests/integration_tests/test_validators.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | from fastapi.testclient import TestClient | ||||||
|  |  | ||||||
|  | from mealie.db.db_setup import create_session | ||||||
|  | from mealie.schema.recipe.recipe import Recipe | ||||||
|  | from tests.utils.fixture_schemas import TestUser | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Routes: | ||||||
|  |     user = "/api/validators/user" | ||||||
|  |     recipe = "/api/validators/recipe" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_validators_user(api_client: TestClient, unique_user: TestUser): | ||||||
|  |     session = create_session() | ||||||
|  |  | ||||||
|  |     # Test existing user | ||||||
|  |     response = api_client.get(Routes.user + f"/{unique_user.username}") | ||||||
|  |     assert response.status_code == 200 | ||||||
|  |     response_data = response.json() | ||||||
|  |     assert not response_data["valid"] | ||||||
|  |  | ||||||
|  |     # Test non-existing user | ||||||
|  |     response = api_client.get(Routes.user + f"/{unique_user.username}2") | ||||||
|  |     assert response.status_code == 200 | ||||||
|  |     response_data = response.json() | ||||||
|  |     assert response_data["valid"] | ||||||
|  |  | ||||||
|  |     session.close() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_validators_recipe(api_client: TestClient, random_recipe: Recipe): | ||||||
|  |     session = create_session() | ||||||
|  |  | ||||||
|  |     # Test existing user | ||||||
|  |     response = api_client.get(Routes.recipe + f"/{random_recipe.group_id}/{random_recipe.slug}") | ||||||
|  |     assert response.status_code == 200 | ||||||
|  |     response_data = response.json() | ||||||
|  |     assert not response_data["valid"] | ||||||
|  |  | ||||||
|  |     # Test non-existing user | ||||||
|  |     response = api_client.get(Routes.recipe + f"/{random_recipe.group_id}/{random_recipe.slug}-test") | ||||||
|  |     assert response.status_code == 200 | ||||||
|  |     response_data = response.json() | ||||||
|  |     assert response_data["valid"] | ||||||
|  |  | ||||||
|  |     session.close() | ||||||
| @@ -7,6 +7,7 @@ from uuid import UUID | |||||||
| class TestUser: | class TestUser: | ||||||
|     email: str |     email: str | ||||||
|     user_id: UUID |     user_id: UUID | ||||||
|  |     username: str | ||||||
|     _group_id: UUID |     _group_id: UUID | ||||||
|     token: Any |     token: Any | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user