From 36db9f2e86c9526419a4c52e2ae7105fa3a82c71 Mon Sep 17 00:00:00 2001 From: Hayden Date: Thu, 7 Jan 2021 19:54:49 -0900 Subject: [PATCH] api documentation --- mealie/models/backup_models.py | 1 - mealie/models/migration_models.py | 12 +++++ mealie/models/recipe_models.py | 56 +++++++++++++++++++++++ mealie/routes/meal_routes.py | 22 +++++---- mealie/routes/migration_routes.py | 5 +- mealie/routes/recipe_routes.py | 68 ++++++++++++++++++++-------- mealie/routes/setting_routes.py | 24 ++++++---- mealie/services/settings_services.py | 28 ++++++++++++ 8 files changed, 175 insertions(+), 41 deletions(-) create mode 100644 mealie/models/migration_models.py create mode 100644 mealie/models/recipe_models.py diff --git a/mealie/models/backup_models.py b/mealie/models/backup_models.py index 3b023d7e9..2bd34b87e 100644 --- a/mealie/models/backup_models.py +++ b/mealie/models/backup_models.py @@ -1,4 +1,3 @@ -# from datetime import datetime from typing import List, Optional from pydantic import BaseModel diff --git a/mealie/models/migration_models.py b/mealie/models/migration_models.py new file mode 100644 index 000000000..52c8fa036 --- /dev/null +++ b/mealie/models/migration_models.py @@ -0,0 +1,12 @@ +from pydantic.main import BaseModel + + +class ChowdownURL(BaseModel): + url: str + + class Config: + schema_extra = { + "example": { + "url": "https://chowdownrepo.com/repo", + } + } diff --git a/mealie/models/recipe_models.py b/mealie/models/recipe_models.py new file mode 100644 index 000000000..c0390d7ae --- /dev/null +++ b/mealie/models/recipe_models.py @@ -0,0 +1,56 @@ +from typing import List, Optional + +import pydantic +from pydantic.main import BaseModel + + +class RecipeResponse(BaseModel): + class Config: + schema_extra = { + "example": [ + { + "slug": "crockpot-buffalo-chicken", + "image": "crockpot-buffalo-chicken.jpg", + "name": "Crockpot Buffalo Chicken", + }, + { + "slug": "downtown-marinade", + "image": "downtown-marinade.jpg", + "name": "Downtown Marinade", + }, + { + "slug": "detroit-style-pepperoni-pizza", + "image": "detroit-style-pepperoni-pizza.jpg", + "name": "Detroit-Style Pepperoni Pizza", + }, + { + "slug": "crispy-carrots", + "image": "crispy-carrots.jpg", + "name": "Crispy Carrots", + }, + ] + } + + +class AllRecipeRequest(BaseModel): + properties: List[str] + limit: Optional[int] + + class Config: + schema_extra = { + "example": { + "properties": ["name", "slug", "image"], + "limit": 100, + } + } + + +class RecipeURLIn(BaseModel): + class Config: + schema_extra = {"example": {"url": "https://myfavoriterecipes.com/recipes"}} + +class SlugResponse(BaseModel): + class Config: + schema_extra = { + "example": "adult-mac-and-cheese" + } \ No newline at end of file diff --git a/mealie/routes/meal_routes.py b/mealie/routes/meal_routes.py index c8a51b3fb..972d7c5d8 100644 --- a/mealie/routes/meal_routes.py +++ b/mealie/routes/meal_routes.py @@ -1,22 +1,23 @@ -from pprint import pprint +from typing import List from fastapi import APIRouter, HTTPException +from models.recipe_models import SlugResponse from services.meal_services import MealPlan from utils.snackbar import SnackResponse router = APIRouter() -@router.get("/api/meal-plan/all/", tags=["Meal Plan"]) +@router.get("/api/meal-plan/all/", tags=["Meal Plan"], response_model=List[MealPlan]) async def get_all_meals(): - """ Returns a list of all available meal plans """ + """ Returns a list of all available Meal Plan """ return MealPlan.get_all() @router.post("/api/meal-plan/create/", tags=["Meal Plan"]) async def set_meal_plan(data: MealPlan): - """ Creates a mealplan database entry""" + """ Creates a meal plan database entry """ data.process_meals() data.save_to_db() @@ -30,7 +31,7 @@ async def set_meal_plan(data: MealPlan): @router.post("/api/meal-plan/{plan_id}/update/", tags=["Meal Plan"]) async def update_meal_plan(plan_id: str, meal_plan: MealPlan): - """ Updates a Meal Plan Based off ID """ + """ Updates a meal plan based off ID """ try: meal_plan.process_meals() @@ -46,21 +47,24 @@ async def update_meal_plan(plan_id: str, meal_plan: MealPlan): @router.delete("/api/meal-plan/{plan_id}/delete/", tags=["Meal Plan"]) async def delete_meal_plan(plan_id): - """ Doc Str """ + """ Removes a meal plan from the database """ MealPlan.delete(plan_id) return SnackResponse.success("Mealplan Deleted") -@router.get("/api/meal-plan/today/", tags=["Meal Plan"]) +@router.get("/api/meal-plan/today/", tags=["Meal Plan"], response_model=SlugResponse) async def get_today(): - """ Returns the meal plan data for today """ + """ + Returns the recipe slug for the meal scheduled for today. + If no meal is scheduled nothing is returned + """ return MealPlan.today() -@router.get("/api/meal-plan/this-week/", tags=["Meal Plan"]) +@router.get("/api/meal-plan/this-week/", tags=["Meal Plan"], response_model=MealPlan) async def get_this_week(): """ Returns the meal plan data for this week """ diff --git a/mealie/routes/migration_routes.py b/mealie/routes/migration_routes.py index 9d30af09d..df2143033 100644 --- a/mealie/routes/migration_routes.py +++ b/mealie/routes/migration_routes.py @@ -1,5 +1,6 @@ from fastapi import APIRouter, HTTPException from models.backup_models import BackupJob +from models.migration_models import ChowdownURL from services.migrations.chowdown import chowdown_migrate as chowdow_migrate from utils.snackbar import SnackResponse @@ -7,10 +8,10 @@ router = APIRouter() @router.post("/api/migration/chowdown/repo/", tags=["Migration"]) -async def import_chowdown_recipes(repo: dict): +async def import_chowdown_recipes(repo: ChowdownURL): """ Import Chowsdown Recipes from Repo URL """ try: - report = chowdow_migrate(repo.get("url")) + report = chowdow_migrate(repo.url) return SnackResponse.success( "Recipes Imported from Git Repo, see report for failures.", additional_data=report, diff --git a/mealie/routes/recipe_routes.py b/mealie/routes/recipe_routes.py index 35ccb406a..240e5c94d 100644 --- a/mealie/routes/recipe_routes.py +++ b/mealie/routes/recipe_routes.py @@ -2,6 +2,12 @@ from typing import List, Optional from fastapi import APIRouter, File, Form, HTTPException, Query from fastapi.responses import FileResponse +from models.recipe_models import ( + AllRecipeRequest, + RecipeResponse, + RecipeURLIn, + SlugResponse, +) from services.image_services import read_image, write_image from services.recipe_services import Recipe, read_requested_values from services.scrape_services import create_from_url @@ -10,17 +16,42 @@ from utils.snackbar import SnackResponse router = APIRouter() -@router.get("/api/all-recipes/", tags=["Recipes"]) +@router.get("/api/all-recipes/", tags=["Recipes"], response_model=RecipeResponse) async def get_all_recipes( keys: Optional[List[str]] = Query(...), num: Optional[int] = 100 -) -> Optional[List[str]]: - """ Returns key data for all recipes """ +): + """ + Returns key data for all recipes based off the query paramters provided. + For example, if slug, image, and name are provided you will recieve a list of + recipes containing the slug, image, and name property. By default, responses + are limited to 100. + + **Note:** You may experience problems with with query parameters. As an alternative + you may also use the post method and provide a body. + See the *Post* method for more details. + """ all_recipes = read_requested_values(keys, num) return all_recipes -@router.get("/api/recipe/{recipe_slug}/", tags=["Recipes"]) +@router.post("/api/all-recipes/", tags=["Recipes"], response_model=RecipeResponse) +async def get_all_recipes_post(body: AllRecipeRequest): + """ + Returns key data for all recipes based off the body data provided. + For example, if slug, image, and name are provided you will recieve a list of + recipes containing the slug, image, and name property. + + Refer to the body example for data formats. + + """ + + all_recipes = read_requested_values(body.properties, body.limit) + + return all_recipes + + +@router.get("/api/recipe/{recipe_slug}/", tags=["Recipes"], response_model=Recipe) async def get_recipe(recipe_slug: str): """ Takes in a recipe slug, returns all data for a recipe """ recipe = Recipe.get_by_slug(recipe_slug) @@ -37,24 +68,21 @@ async def get_recipe_img(recipe_slug: str): # Recipe Creations -@router.post("/api/recipe/create-url/", tags=["Recipes"], status_code=201) -async def get_recipe_url(url: dict): - """ Takes in a URL and Attempts to scrape data and load it into the database """ +@router.post( + "/api/recipe/create-url/", + tags=["Recipes"], + status_code=201, + response_model=SlugResponse, +) +async def parse_recipe_url(url: RecipeURLIn): + """ Takes in a URL and attempts to scrape data and load it into the database """ - url = url.get("url") - slug = create_from_url(url) - - # try: - # slug = create_from_url(url) - # except: - # raise HTTPException( - # status_code=400, detail=SnackResponse.error("Unable to Parse URL") - # ) + slug = create_from_url(url.url) return slug -@router.post("/api/recipe/create/", tags=["Recipes"]) +@router.post("/api/recipe/create/", tags=["Recipes"], response_model=SlugResponse) async def create_from_json(data: Recipe) -> str: """ Takes in a JSON string and loads data into the database as a new entry""" created_recipe = data.save_to_db() @@ -63,7 +91,7 @@ async def create_from_json(data: Recipe) -> str: @router.post("/api/recipe/{recipe_slug}/update/image/", tags=["Recipes"]) -def update_image( +def update_recipe_image( recipe_slug: str, image: bytes = File(...), extension: str = Form(...) ): """ Removes an existing image and replaces it with the incoming file. """ @@ -73,7 +101,7 @@ def update_image( @router.post("/api/recipe/{recipe_slug}/update/", tags=["Recipes"]) -async def update(recipe_slug: str, data: Recipe): +async def update_recipe(recipe_slug: str, data: Recipe): """ Updates a recipe by existing slug and data. Data should containt """ data.update(recipe_slug) @@ -82,7 +110,7 @@ async def update(recipe_slug: str, data: Recipe): @router.delete("/api/recipe/{recipe_slug}/delete/", tags=["Recipes"]) -async def delete(recipe_slug: str): +async def delete_recipe(recipe_slug: str): """ Deletes a recipe by slug """ try: diff --git a/mealie/routes/setting_routes.py b/mealie/routes/setting_routes.py index 6a254c4e2..742bd889f 100644 --- a/mealie/routes/setting_routes.py +++ b/mealie/routes/setting_routes.py @@ -1,3 +1,5 @@ +from typing import List + from db.mongo_setup import global_init from fastapi import APIRouter, HTTPException from services.scheduler_services import Scheduler, post_webhooks @@ -11,16 +13,16 @@ scheduler = Scheduler() scheduler.startup_scheduler() -@router.get("/api/site-settings/", tags=["Settings"]) +@router.get("/api/site-settings/", tags=["Settings"], response_model=SiteSettings) async def get_main_settings(): - """ Returns basic site Settings """ + """ Returns basic site settings """ return SiteSettings.get_site_settings() @router.post("/api/site-settings/webhooks/test/", tags=["Settings"]) async def test_webhooks(): - """ Test Webhooks """ + """ Run the function to test your webhooks """ return post_webhooks() @@ -40,22 +42,26 @@ async def update_settings(data: SiteSettings): return SnackResponse.success("Settings Updated") -@router.get("/api/site-settings/themes/", tags=["Themes"]) +@router.get( + "/api/site-settings/themes/", tags=["Themes"], response_model=List[SiteTheme] +) async def get_all_themes(): """ Returns all site themes """ return SiteTheme.get_all() -@router.get("/api/site-settings/themes/{theme_name}/", tags=["Themes"]) +@router.get( + "/api/site-settings/themes/{theme_name}/", tags=["Themes"], response_model=SiteTheme +) async def get_single_theme(theme_name: str): - """ Returns basic site Settings """ + """ Returns a named theme """ return SiteTheme.get_by_name(theme_name) @router.post("/api/site-settings/themes/create/", tags=["Themes"]) async def create_theme(data: SiteTheme): - """ Creates a Site Color Theme """ + """ Creates a site color theme database entry """ try: data.save_to_db() @@ -69,7 +75,7 @@ async def create_theme(data: SiteTheme): @router.post("/api/site-settings/themes/{theme_name}/update/", tags=["Themes"]) async def update_theme(theme_name: str, data: SiteTheme): - """ Returns basic site Settings """ + """ Update a theme database entry """ try: data.update_document() except: @@ -82,7 +88,7 @@ async def update_theme(theme_name: str, data: SiteTheme): @router.delete("/api/site-settings/themes/{theme_name}/delete/", tags=["Themes"]) async def delete_theme(theme_name: str): - """ Returns basic site Settings """ + """ Deletes theme from the database """ try: SiteTheme.delete_theme(theme_name) except: diff --git a/mealie/services/settings_services.py b/mealie/services/settings_services.py index b2b55109b..46e0992cb 100644 --- a/mealie/services/settings_services.py +++ b/mealie/services/settings_services.py @@ -24,6 +24,18 @@ class SiteSettings(BaseModel): name: str = "main" webhooks: Webhooks + class Config: + schema_extra = { + "example": { + "name": "main", + "webhooks": { + "webhookTime": "00:00", + "webhookURLs": ["https://mywebhookurl.com/webhook"], + "enable": False, + }, + } + } + @staticmethod def _unpack_doc(document: SiteSettingsDocument): document = json.loads(document.to_json()) @@ -65,6 +77,22 @@ class SiteTheme(BaseModel): name: str colors: Colors + class Config: + schema_extra = { + "example": { + "name": "default", + "colors": { + "primary": "#E58325", + "accent": "#00457A", + "secondary": "#973542", + "success": "#5AB1BB", + "info": "#4990BA", + "warning": "#FF4081", + "error": "#EF5350", + }, + } + } + @staticmethod def get_by_name(theme_name): document = SiteThemeDocument.objects.get(name=theme_name)