diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..b90fca4d2 --- /dev/null +++ b/.flake8 @@ -0,0 +1,6 @@ +[flake8] +ignore = [ + E501 # Line Length - See Black Config in pyproject.toml + E722 # Bare Exception | Temporary +] +exclude = _all_models.py diff --git a/dev/scripts/api_docs_gen.py b/dev/scripts/api_docs_gen.py new file mode 100644 index 000000000..fb5300306 --- /dev/null +++ b/dev/scripts/api_docs_gen.py @@ -0,0 +1,44 @@ +import json + +from mealie.app import app +from mealie.core.config import DATA_DIR + +"""Script to export the ReDoc documentation page into a standalone HTML file.""" + +HTML_TEMPLATE = """ +{% extends "main.html" %} +{% block tabs %} +{{ super() }} + + + + +
+ + + + +{% endblock %} +{% block content %}{% endblock %} +{% block footer %}{% endblock %} +""" + +HTML_PATH = DATA_DIR.parent.parent.joinpath("docs/docs/overrides/api.html") + + +def generate_api_docs(my_app): + with open(HTML_PATH, "w") as fd: + text = HTML_TEMPLATE.replace("MY_SPECIFIC_TEXT", json.dumps(my_app.openapi())) + fd.write(text) + + +if __name__ == "__main__": + generate_api_docs(app) diff --git a/dev/scripts/app_routes_gen.py b/dev/scripts/app_routes_gen.py new file mode 100644 index 000000000..c15267d90 --- /dev/null +++ b/dev/scripts/app_routes_gen.py @@ -0,0 +1,83 @@ +import json +import re +from pathlib import Path +from typing import Optional + +import slugify +from jinja2 import Template +from mealie.app import app +from pydantic import BaseModel + +CWD = Path(__file__).parent +OUT_FILE = CWD.joinpath("output", "app_routes.py") + +code_template = """ +class AppRoutes: + def __init__(self) -> None: + self.prefix = '{{paths.prefix}}' +{% for path in paths.static_paths %} + self.{{ path.router_slug }} = "{{path.prefix}}{{ path.route }}"{% endfor %} +{% for path in paths.function_paths %} + def {{path.router_slug}}(self, {{path.var|join(", ")}}): + return f"{self.prefix}{{ path.route }}" +{% endfor %} +""" + + +def get_variables(path): + path = path.replace("/", " ") + print(path) + var = re.findall(r" \{.*\}", path) + print(var) + if var: + return [v.replace("{", "").replace("}", "") for v in var] + else: + return None + + +class RouteObject: + def __init__(self, route_string) -> None: + self.prefix = "/" + route_string.split("/")[1] + self.route = route_string.replace(self.prefix, "") + self.parts = route_string.split("/")[1:] + self.var = re.findall(r"\{(.*?)\}", route_string) + self.is_function = "{" in self.route + self.router_slug = slugify.slugify("_".join(self.parts[1:]), separator="_") + + def __repr__(self) -> str: + return f"""Route: {self.route} +Parts: {self.parts} +Function: {self.is_function} +Var: {self.var} +Slug: {self.router_slug} +""" + + +def get_paths(app): + paths = [] + print(json.dumps(app.openapi())) + for key, value in app.openapi().items(): + if key == "paths": + for key, value in value.items(): + paths.append(key) + + return paths + + +def generate_template(app): + paths = get_paths(app) + new_paths = [RouteObject(path) for path in paths] + + static_paths = [p for p in new_paths if not p.is_function] + function_paths = [p for p in new_paths if p.is_function] + + template = Template(code_template) + + content = template.render(paths={"prefix": "/api", "static_paths": static_paths, "function_paths": function_paths}) + + with open(OUT_FILE, "w") as f: + f.write(content) + + +if __name__ == "__main__": + generate_template(app) diff --git a/dev/scripts/output/app_routes.py b/dev/scripts/output/app_routes.py new file mode 100644 index 000000000..6c43040c9 --- /dev/null +++ b/dev/scripts/output/app_routes.py @@ -0,0 +1,97 @@ +class AppRoutes: + def __init__(self) -> None: + self.prefix = "/api" + + self.users_sign_ups = "/api/users/sign-ups" + self.auth_token = "/api/auth/token" + self.auth_token_long = "/api/auth/token/long" + self.auth_refresh = "/api/auth/refresh" + self.users = "/api/users" + self.users_self = "/api/users/self" + self.groups = "/api/groups" + self.groups_self = "/api/groups/self" + self.recipes = "/api/recipes" + self.recipes_category = "/api/recipes/category" + self.recipes_tag = "/api/recipes/tag" + self.categories = "/api/categories" + self.recipes_tags = "/api/recipes/tags/" + self.recipes_create = "/api/recipes/create" + self.recipes_create_url = "/api/recipes/create-url" + self.meal_plans_all = "/api/meal-plans/all" + self.meal_plans_create = "/api/meal-plans/create" + self.meal_plans_this_week = "/api/meal-plans/this-week" + self.meal_plans_today = "/api/meal-plans/today" + self.site_settings_custom_pages = "/api/site-settings/custom-pages" + self.site_settings = "/api/site-settings" + self.site_settings_webhooks_test = "/api/site-settings/webhooks/test" + self.themes = "/api/themes" + self.themes_create = "/api/themes/create" + self.backups_available = "/api/backups/available" + self.backups_export_database = "/api/backups/export/database" + self.backups_upload = "/api/backups/upload" + self.migrations = "/api/migrations" + self.debug_version = "/api/debug/version" + self.debug_last_recipe_json = "/api/debug/last-recipe-json" + + def users_sign_ups_token(self, token): + return f"{self.prefix}/users/sign-ups/{token}" + + def users_id(self, id): + return f"{self.prefix}/users/{id}" + + def users_id_reset_password(self, id): + return f"{self.prefix}/users/{id}/reset-password" + + def users_id_image(self, id): + return f"{self.prefix}/users/{id}/image" + + def users_id_password(self, id): + return f"{self.prefix}/users/{id}/password" + + def groups_id(self, id): + return f"{self.prefix}/groups/{id}" + + def categories_category(self, category): + return f"{self.prefix}/categories/{category}" + + def recipes_tags_tag(self, tag): + return f"{self.prefix}/recipes/tags/{tag}" + + def recipes_recipe_slug(self, recipe_slug): + return f"{self.prefix}/recipes/{recipe_slug}" + + def recipes_recipe_slug_image(self, recipe_slug): + return f"{self.prefix}/recipes/{recipe_slug}/image" + + def meal_plans_plan_id(self, plan_id): + return f"{self.prefix}/meal-plans/{plan_id}" + + def meal_plans_id_shopping_list(self, id): + return f"{self.prefix}/meal-plans/{id}/shopping-list" + + def site_settings_custom_pages_id(self, id): + return f"{self.prefix}/site-settings/custom-pages/{id}" + + def themes_theme_name(self, theme_name): + return f"{self.prefix}/themes/{theme_name}" + + def backups_file_name_download(self, file_name): + return f"{self.prefix}/backups/{file_name}/download" + + def backups_file_name_import(self, file_name): + return f"{self.prefix}/backups/{file_name}/import" + + def backups_file_name_delete(self, file_name): + return f"{self.prefix}/backups/{file_name}/delete" + + def migrations_type_file_name_import(self, type, file_name): + return f"{self.prefix}/migrations/{type}/{file_name}/import" + + def migrations_type_file_name_delete(self, type, file_name): + return f"{self.prefix}/migrations/{type}/{file_name}/delete" + + def migrations_type_upload(self, type): + return f"{self.prefix}/migrations/{type}/upload" + + def debug_log_num(self, num): + return f"{self.prefix}/debug/log/{num}" diff --git a/docs/docs/overrides/api.html b/docs/docs/overrides/api.html index fefa395b0..50778c3f3 100644 --- a/docs/docs/overrides/api.html +++ b/docs/docs/overrides/api.html @@ -1,5 +1,4 @@ - {% extends "main.html" %} {% block tabs %} {{ super() }} @@ -10,14 +9,16 @@ padding: 0; } - - - - + + + + + + {% endblock %} {% block content %}{% endblock %} -{% block footer %}{% endblock %} \ No newline at end of file +{% block footer %}{% endblock %} diff --git a/frontend/jsconfig.json b/frontend/jsconfig.json index e69de29bb..0486e3bb3 100644 --- a/frontend/jsconfig.json +++ b/frontend/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} diff --git a/frontend/src/api/backup.js b/frontend/src/api/backup.js index 2b00d0d03..5512f512e 100644 --- a/frontend/src/api/backup.js +++ b/frontend/src/api/backup.js @@ -14,25 +14,46 @@ const backupURLs = { }; export default { + /** + * Request all backups available on the server + * @returns {Array} List of Available Backups + */ async requestAvailable() { let response = await apiReq.get(backupURLs.available); return response.data; }, - + /** + * Calls for importing a file on the server + * @param {string} fileName + * @param {object} data + * @returns A report containing status of imported items + */ async import(fileName, data) { let response = await apiReq.post(backupURLs.importBackup(fileName), data); store.dispatch("requestRecentRecipes"); return response; }, - + /** + * Removes a file from the server + * @param {string} fileName + */ async delete(fileName) { await apiReq.delete(backupURLs.deleteBackup(fileName)); }, - - async create(data) { - let response = apiReq.post(backupURLs.createBackup, data); + /** + * Creates a backup on the serve given a set of options + * @param {object} data + * @returns + */ + async create(options) { + let response = apiReq.post(backupURLs.createBackup, options); return response; }, + /** + * Downloads a file from the server. I don't actually think this is used? + * @param {string} fileName + * @returns Download URL + */ async download(fileName) { let response = await apiReq.get(backupURLs.downloadBackup(fileName)); return response.data; diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index cce731518..fc224be9b 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -12,7 +12,10 @@ import signUps from "./signUps"; import groups from "./groups"; import siteSettings from "./siteSettings"; -export default { +/** + * The main object namespace for interacting with the backend database + */ +export const api = { recipes: recipe, siteSettings: siteSettings, backups: backup, diff --git a/frontend/src/api/recipe.js b/frontend/src/api/recipe.js index 225f5a81d..cdaed9f23 100644 --- a/frontend/src/api/recipe.js +++ b/frontend/src/api/recipe.js @@ -19,6 +19,11 @@ const recipeURLs = { }; export default { + /** + * Create a Recipe by URL + * @param {string} recipeURL + * @returns {string} Recipe Slug + */ async createByURL(recipeURL) { let response = await apiReq.post(recipeURLs.createByURL, { url: recipeURL, diff --git a/frontend/src/api/upload.js b/frontend/src/api/upload.js index 12a1c08c2..53918844b 100644 --- a/frontend/src/api/upload.js +++ b/frontend/src/api/upload.js @@ -1,7 +1,7 @@ import { apiReq } from "./api-utils"; export default { - // import api from "@/api"; + // import { api } from "@/api"; async uploadFile(url, fileObject) { let response = await apiReq.post(url, fileObject, { headers: { diff --git a/frontend/src/components/Admin/AdminSidebar.vue b/frontend/src/components/Admin/AdminSidebar.vue index fec699d86..5e719307b 100644 --- a/frontend/src/components/Admin/AdminSidebar.vue +++ b/frontend/src/components/Admin/AdminSidebar.vue @@ -106,7 +106,7 @@ import { validators } from "@/mixins/validators"; import { initials } from "@/mixins/initials"; import { user } from "@/mixins/user"; -import api from "@/api"; +import { api } from "@/api"; import axios from "axios"; export default { mixins: [validators, initials, user], diff --git a/frontend/src/components/Admin/Backup/AvailableBackupCard.vue b/frontend/src/components/Admin/Backup/AvailableBackupCard.vue index 2f30e5823..499d2c4cd 100644 --- a/frontend/src/components/Admin/Backup/AvailableBackupCard.vue +++ b/frontend/src/components/Admin/Backup/AvailableBackupCard.vue @@ -39,7 +39,7 @@ - -