1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-07-24 15:49:42 +02:00

chore: file generation cleanup (#1736)

This PR does too many things :( 

1. Major refactoring of the dev/scripts and dev/code-generation folders. 

Primarily this was removing duplicate code and cleaning up some poorly written code snippets as well as making them more idempotent so then can be re-run over and over again but still maintain the same results. This is working on my machine, but I've been having problems in CI and comparing diffs so running generators in CI will have to wait. 

2. Re-Implement using the generated api routes for testing

This was a _huge_ refactor that touched damn near every test file but now we have auto-generated typed routes with inline hints and it's used for nearly every test excluding a few that use classes for better parameterization. This should greatly reduce errors when writing new tests. 

3. Minor Perf improvements for the All Recipes endpoint

  A. Removed redundant loops
  B. Uses orjson to do the encoding directly and returns a byte response instead of relying on the default 
       jsonable_encoder.

4. Fix some TS type errors that cropped up for seemingly no reason half way through the PR.

See this issue https://github.com/phillipdupuis/pydantic-to-typescript/issues/28

Basically, the generated TS type is not-correct since Pydantic will automatically fill in null fields. The resulting TS type is generated with a ? to indicate it can be null even though we _know_ that i can't be.
This commit is contained in:
Hayden 2022-10-18 14:49:41 -08:00 committed by GitHub
parent a8f0fb14a7
commit 9ecef4c25f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
107 changed files with 2520 additions and 1948 deletions

View file

@ -2,8 +2,13 @@ import json
import random
import string
import time
from dataclasses import dataclass
import requests
from rich.console import Console
from rich.table import Table
console = Console()
def random_string(length: int) -> str:
@ -14,6 +19,218 @@ def payload_factory() -> dict:
return {"name": random_string(15)}
def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dict:
return {
"id": id,
"userId": userId,
"groupId": groupId,
"name": name,
"slug": slug,
"image": "tNRG",
"recipeYield": "9 servings",
"totalTime": "33 Minutes",
"prepTime": "20 Minutes",
"cookTime": None,
"performTime": "13 Minutes",
"description": "These Levain Bakery-Style Peanut Butter Cookies are the ULTIMATE for serious PB lovers! Supremely thick and chewy with gooey centers and a soft texture, they're packed with peanut butter flavor and Reese's Pieces for the most amazing cookie ever!",
"recipeCategory": [],
"tags": [],
"tools": [],
"rating": None,
"orgURL": "https://thedomesticrebel.com/2021/04/28/levain-bakery-style-ultimate-peanut-butter-cookies/",
"recipeIngredient": [
{
"title": None,
"note": "1 cup unsalted butter, cut into cubes",
"unit": None,
"food": None,
"disableAmount": True,
"quantity": 1,
"originalText": None,
"referenceId": "ea3b6702-9532-4fbc-a40b-f99917831c26",
},
{
"title": None,
"note": "1 cup light brown sugar",
"unit": None,
"food": None,
"disableAmount": True,
"quantity": 1,
"originalText": None,
"referenceId": "c5bbfefb-1e23-4ffd-af88-c0363a0fae82",
},
{
"title": None,
"note": "1/2 cup granulated white sugar",
"unit": None,
"food": None,
"disableAmount": True,
"quantity": 1,
"originalText": None,
"referenceId": "034f481b-c426-4a17-b983-5aea9be4974b",
},
{
"title": None,
"note": "2 large eggs",
"unit": None,
"food": None,
"disableAmount": True,
"quantity": 1,
"originalText": None,
"referenceId": "37c1f796-3bdb-4856-859f-dbec90bc27e4",
},
{
"title": None,
"note": "2 tsp vanilla extract",
"unit": None,
"food": None,
"disableAmount": True,
"quantity": 1,
"originalText": None,
"referenceId": "85561ace-f249-401d-834c-e600a2f6280e",
},
{
"title": None,
"note": "1/2 cup creamy peanut butter",
"unit": None,
"food": None,
"disableAmount": True,
"quantity": 1,
"originalText": None,
"referenceId": "ac91bda0-e8a8-491a-976a-ae4e72418cfd",
},
{
"title": None,
"note": "1 tsp cornstarch",
"unit": None,
"food": None,
"disableAmount": True,
"quantity": 1,
"originalText": None,
"referenceId": "4d1256b3-115e-4475-83cd-464fbc304cb0",
},
{
"title": None,
"note": "1 tsp baking soda",
"unit": None,
"food": None,
"disableAmount": True,
"quantity": 1,
"originalText": None,
"referenceId": "64627441-39f9-4ee3-8494-bafe36451d12",
},
{
"title": None,
"note": "1/2 tsp salt",
"unit": None,
"food": None,
"disableAmount": True,
"quantity": 1,
"originalText": None,
"referenceId": "7ae212d0-3cd1-44b0-899e-ec5bd91fd384",
},
{
"title": None,
"note": "1 cup cake flour",
"unit": None,
"food": None,
"disableAmount": True,
"quantity": 1,
"originalText": None,
"referenceId": "06967994-8548-4952-a8cc-16e8db228ebd",
},
{
"title": None,
"note": "2 cups all-purpose flour",
"unit": None,
"food": None,
"disableAmount": True,
"quantity": 1,
"originalText": None,
"referenceId": "bdb33b23-c767-4465-acf8-3b8e79eb5691",
},
{
"title": None,
"note": "2 cups peanut butter chips",
"unit": None,
"food": None,
"disableAmount": True,
"quantity": 1,
"originalText": None,
"referenceId": "12ba0af8-affd-4fb2-9cca-6f1b3e8d3aef",
},
{
"title": None,
"note": "1½ cups Reese's Pieces candies",
"unit": None,
"food": None,
"disableAmount": True,
"quantity": 1,
"originalText": None,
"referenceId": "4bdc0598-a3eb-41ee-8af0-4da9348fbfe2",
},
],
"dateAdded": "2022-09-03",
"dateUpdated": "2022-09-10T15:18:19.866085",
"createdAt": "2022-09-03T18:31:17.488118",
"updateAt": "2022-09-10T15:18:19.869630",
"recipeInstructions": [
{
"id": "60ae53a3-b3ff-40ee-bae3-89fea0b1c637",
"title": "",
"text": "Preheat oven to 410° degrees F. Line 2 baking sheets with parchment paper or silicone liners; set aside.",
"ingredientReferences": [],
},
{
"id": "4e1c30c2-2e96-4a0a-b750-23c9ea3640f8",
"title": "",
"text": "In the bowl of a stand mixer, cream together the cubed butter, brown sugar and granulated sugar with the paddle attachment for 30 seconds on low speed. Increase speed to medium and beat for another 30 seconds, then increase to medium-high speed and beat for another 30 seconds until mixture is creamy and smooth. Beat in the eggs, one at a time, followed by the vanilla extract and peanut butter, scraping down the sides and bottom of the bowl as needed.",
"ingredientReferences": [],
},
{
"id": "9fb8e2a2-d410-445c-bafc-c059203e6f4b",
"title": "",
"text": "Add in the cornstarch, baking soda, salt, cake flour, and all-purpose flour and mix on low speed until just combined. Fold in the peanut butter chips and Reese's Pieces candies by hand until fully incorporated. Chill the dough uncovered in the fridge for 15 minutes.",
"ingredientReferences": [],
},
{
"id": "1ceb9aa4-49f7-4d4a-996f-3c715eb74642",
"title": "",
"text": 'Using a digital kitchen scale for accuracy, weigh out 6 ounces of cookie dough in a loose, rough textured ball. I like to make my cookie dough balls kind of tall as well. You do not want the dough balls to be smooth and compacted. Place on the baking sheet. Repeat with remaining dough balls, staggering on the baking sheet at least 3" apart from one another, and only placing 4 dough balls per baking sheet.',
"ingredientReferences": [],
},
{
"id": "591993fc-72bb-4091-8a12-84640c523fc1",
"title": "",
"text": "Bake one baking sheet at a time in the center rack of the oven for 10-13 minutes or until the tops are light golden brown and the exterior is dry and dull looking. Centers will be slightly underdone and gooey; this is okay and the cookies will finish cooking some once removed from the oven. Let stand on the baking sheets for at least 30 minutes before serving; the cookies are very delicate and fragile once removed from the oven and need time to set before being moved. Keep remaining dough refrigerated while other cookies bake.",
"ingredientReferences": [],
},
],
"nutrition": {
"calories": None,
"fatContent": None,
"proteinContent": None,
"carbohydrateContent": None,
"fiberContent": None,
"sodiumContent": None,
"sugarContent": None,
},
"settings": {
"public": True,
"showNutrition": False,
"showAssets": False,
"landscapeView": False,
"disableComments": False,
"disableAmount": True,
"locked": False,
},
"assets": [],
"notes": [],
"extras": {},
"comments": [],
}
def login(username="changeme@email.com", password="MyPassword"):
payload = {"username": username, "password": password}
@ -30,32 +247,90 @@ def populate_data(token):
r = requests.post("http://localhost:9000/api/recipes", json=payload, headers=token)
if r.status_code != 201:
print(f"Error: {r.status_code}")
print(r.text)
console.print(f"Error: {r.status_code}")
console.print(r.text)
exit()
else:
print(f"Created recipe: {payload}")
recipe_json = requests.get(f"http://localhost:9000/api/recipes/{payload['name']}", headers=token)
if recipe_json.status_code != 200:
console.print(f"Error: {recipe_json.status_code}")
console.print(recipe_json.text)
exit()
recipe = json.loads(recipe_json.text)
update_data = recipe_data(recipe["name"], recipe["slug"], recipe["id"], recipe["userId"], recipe["groupId"])
r = requests.put(f"http://localhost:9000/api/recipes/{update_data['slug']}", json=update_data, headers=token)
if r.status_code != 200:
console.print(f"Error: {r.status_code}")
console.print(r.text)
exit()
def time_request(url, headers):
@dataclass(slots=True)
class Result:
recipes: int
time: float
def time_request(url, headers) -> Result:
start = time.time()
r = requests.get(url, headers=headers)
print(f"Total Recipes {len(r.json())}")
end = time.time()
print(end - start)
return Result(len(r.json()["items"]), end - start)
def main():
print("Starting...")
token = login()
# populate_data(token)
for _ in range(10):
time_request("http://localhost:9000/api/recipes", token)
results: list[Result] = []
print("Finished...")
for _ in range(10):
result = time_request("http://localhost:9000/api/recipes?perPage=-1&page=1&loadFood=true", token)
results.append(result)
min, max, average = 99, 0, 0
for result in results:
if result.time < min:
min = result.time
if result.time > max:
max = result.time
average += result.time
tbl1 = Table(title="Requests")
tbl1.add_column("Recipes", justify="right", style="cyan", no_wrap=True)
tbl1.add_column("Time", justify="right", style="magenta")
for result in results:
tbl1.add_row(
str(result.recipes),
str(result.time),
)
tbl2 = Table(title="Summary")
tbl2.add_column("Min", justify="right", style="green")
tbl2.add_column("Max", justify="right", style="green")
tbl2.add_column("Average", justify="right", style="green")
tbl2.add_row(
str(round(min * 1000)) + "ms",
str(round(max * 1000)) + "ms",
str(round((average / len(results)) * 1000)) + "ms",
)
console = Console()
console.print(tbl1)
console.print(tbl2)
# Best Time 289 / 405/ 247
if __name__ == "__main__":

View file

@ -1,46 +0,0 @@
import json
from mealie.app import app
from mealie.core.config import determine_data_dir
DATA_DIR = determine_data_dir()
"""Script to export the ReDoc documentation page into a standalone HTML file."""
HTML_TEMPLATE = """<!-- Custom HTML site displayed as the Home chapter -->
{% extends "main.html" %}
{% block tabs %}
{{ super() }}
<style>
body {
margin: 0;
padding: 0;
}
</style>
<div id="redoc-container"></div>
<script src="https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js"> </script>
<script>
var spec = MY_SPECIFIC_TEXT;
Redoc.init(spec, {}, document.getElementById("redoc-container"));
</script>
{% 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)

View file

@ -1,214 +0,0 @@
import json
import re
from enum import Enum
from itertools import groupby
from pathlib import Path
from typing import Optional
from fastapi import FastAPI
from humps import camelize
from jinja2 import Template
from pydantic import BaseModel, Field
from slugify import slugify
from mealie.app import app
CWD = Path(__file__).parent
OUT_DIR = CWD / "output"
TEMPLATES_DIR = CWD / "templates"
JS_DIR = OUT_DIR / "javascriptAPI"
JS_DIR.mkdir(exist_ok=True, parents=True)
class RouteObject:
def __init__(self, route_string) -> None:
self.prefix = "/" + route_string.split("/")[1]
self.route = "/" + route_string.split("/", 2)[2]
self.js_route = self.route.replace("{", "${")
self.parts = route_string.split("/")[1:]
self.var = re.findall(r"\{(.*?)\}", route_string)
self.is_function = "{" in self.route
self.router_slug = slugify("_".join(self.parts[1:]), separator="_")
self.router_camel = camelize(self.router_slug)
class RequestType(str, Enum):
get = "get"
put = "put"
post = "post"
patch = "patch"
delete = "delete"
class ParameterIn(str, Enum):
query = "query"
path = "path"
class RouterParameter(BaseModel):
required: bool = False
name: str
location: ParameterIn = Field(..., alias="in")
class RequestBody(BaseModel):
required: bool = False
class HTTPRequest(BaseModel):
request_type: RequestType
description: str = ""
summary: str
requestBody: Optional[RequestBody]
parameters: list[RouterParameter] = []
tags: list[str]
def list_as_js_object_string(self, parameters, braces=True):
if len(parameters) == 0:
return ""
if braces:
return "{" + ", ".join(parameters) + "}"
else:
return ", ".join(parameters)
def payload(self):
return "payload" if self.requestBody else ""
def function_args(self):
all_params = [p.name for p in self.parameters]
if self.requestBody:
all_params.append("payload")
return self.list_as_js_object_string(all_params)
def query_params(self):
params = [param.name for param in self.parameters if param.location == ParameterIn.query]
return self.list_as_js_object_string(params)
def path_params(self):
params = [param.name for param in self.parameters if param.location == ParameterIn.path]
return self.list_as_js_object_string(parameters=params, braces=False)
@property
def summary_camel(self):
return camelize(slugify(self.summary))
@property
def js_docs(self):
return self.description.replace("\n", " \n * ")
class PathObject(BaseModel):
route_object: RouteObject
http_verbs: list[HTTPRequest]
class Config:
arbitrary_types_allowed = True
def get_path_objects(app: FastAPI):
paths = []
for key, value in app.openapi().items():
if key == "paths":
for key, value in value.items():
paths.append(
PathObject(
route_object=RouteObject(key),
http_verbs=[HTTPRequest(request_type=k, **v) for k, v in value.items()],
)
)
return paths
def dump_open_api(app: FastAPI):
"""Writes the Open API as JSON to a json file"""
OPEN_API_FILE = CWD / "openapi.json"
with open(OPEN_API_FILE, "w") as f:
f.write(json.dumps(app.openapi()))
def read_template(file: Path):
with open(file, "r") as f:
return f.read()
def generate_python_templates(static_paths: list[PathObject], function_paths: list[PathObject]):
PYTEST_TEMPLATE = TEMPLATES_DIR / "pytest_routes.j2"
PYTHON_OUT_FILE = OUT_DIR / "app_routes.py"
template = Template(read_template(PYTEST_TEMPLATE))
content = template.render(
paths={
"prefix": "/api",
"static_paths": static_paths,
"function_paths": function_paths,
}
)
with open(PYTHON_OUT_FILE, "w") as f:
f.write(content)
return
def generate_js_templates(paths: list[PathObject]):
# Template Path
JS_API_INTERFACE = TEMPLATES_DIR / "js_api_interface.j2"
JS_INDEX = TEMPLATES_DIR / "js_index.j2"
INTERFACES_DIR = JS_DIR / "interfaces"
INTERFACES_DIR.mkdir(exist_ok=True, parents=True)
all_tags = []
for tag, tag_paths in groupby(paths, lambda x: x.http_verbs[0].tags[0]):
file_name = slugify(tag, separator="-")
tag = camelize(tag)
tag_paths: list[PathObject] = list(tag_paths)
template = Template(read_template(JS_API_INTERFACE))
content = template.render(
paths={
"prefix": "/api",
"static_paths": [x.route_object for x in tag_paths if not x.route_object.is_function],
"function_paths": [x.route_object for x in tag_paths if x.route_object.is_function],
"all_paths": tag_paths,
"export_name": tag,
}
)
tag: dict = {"camel": camelize(tag), "slug": file_name}
all_tags.append(tag)
with open(INTERFACES_DIR.joinpath(file_name + ".ts"), "w") as f:
f.write(content)
template = Template(read_template(JS_INDEX))
content = template.render(files={"files": all_tags})
with open(JS_DIR.joinpath("index.js"), "w") as f:
f.write(content)
def generate_template(app):
dump_open_api(app)
paths = get_path_objects(app)
static_paths = [x.route_object for x in paths if not x.route_object.is_function]
function_paths = [x.route_object for x in paths if x.route_object.is_function]
static_paths.sort(key=lambda x: x.router_slug)
function_paths.sort(key=lambda x: x.router_slug)
generate_python_templates(static_paths, function_paths)
generate_js_templates(paths)
if __name__ == "__main__":
generate_template(app)

View file

@ -1,26 +0,0 @@
import json
from pathlib import Path
import requests
CWD = Path(__file__).parent
def login(username="changeme@email.com", password="MyPassword"):
payload = {"username": username, "password": password}
r = requests.post("http://localhost:9000/api/auth/token", payload)
# Bearer
token = json.loads(r.text).get("access_token")
return {"Authorization": f"Bearer {token}"}
def main():
print("Starting...")
print("Finished...")
if __name__ == "__main__":
main()

View file

@ -1,159 +0,0 @@
import json
import re
from dataclasses import dataclass
from pathlib import Path
from slugify import slugify
CWD = Path(__file__).parent
PROJECT_BASE = CWD.parent.parent
server_side_msgs = PROJECT_BASE / "mealie" / "utils" / "error_messages.py"
en_us_msgs = PROJECT_BASE / "frontend" / "lang" / "errors" / "en-US.json"
client_side_msgs = PROJECT_BASE / "frontend" / "utils" / "error-messages.ts"
GENERATE_MESSAGES = [
# User Related
"user",
"webhook",
"token",
# Group Related
"group",
"cookbook",
"mealplan",
# Recipe Related
"scraper",
"recipe",
"ingredient",
"food",
"unit",
# Admin Related
"backup",
"migration",
"event",
]
class ErrorMessage:
def __init__(self, prefix, verb) -> None:
self.message = f"{prefix.title()} {verb.title()} Failed"
self.snake = slugify(self.message, separator="_")
self.kabab = slugify(self.message, separator="-")
def factory(prefix) -> list["ErrorMessage"]:
verbs = ["Create", "Update", "Delete"]
return [ErrorMessage(prefix, verb) for verb in verbs]
@dataclass
class CodeGenLines:
start: int
end: int
indentation: str
text: list[str]
_next_line = None
def purge_lines(self) -> None:
start = self.start + 1
end = self.end
del self.text[start:end]
def push_line(self, string: str) -> None:
self._next_line = self._next_line or self.start + 1
self.text.insert(self._next_line, self.indentation + string)
self._next_line += 1
def find_start(file_text: list[str], gen_id: str):
for x, line in enumerate(file_text):
if "CODE_GEN_ID:" in line and gen_id in line:
return x, line
return None
def find_end(file_text: list[str], gen_id: str):
for x, line in enumerate(file_text):
if f"END {gen_id}" in line:
return x, line
return None
def get_indentation_of_string(line: str):
return re.sub(r"#.*", "", line).removesuffix("\n")
def get_messages(message_prefix: str) -> str:
prefix = message_prefix.lower()
return [
f'{prefix}_create_failure = "{prefix}-create-failure"\n',
f'{prefix}_update_failure = "{prefix}-update-failure"\n',
f'{prefix}_delete_failure = "{prefix}-delete-failure"\n',
]
def code_gen_factory(file_path: Path) -> CodeGenLines:
with open(file_path, "r") as file:
text = file.readlines()
start_num, line = find_start(text, "ERROR_MESSAGE_ENUMS")
indentation = get_indentation_of_string(line)
end_num, line = find_end(text, "ERROR_MESSAGE_ENUMS")
return CodeGenLines(
start=start_num,
end=end_num,
indentation=indentation,
text=text,
)
def write_to_locals(messages: list[ErrorMessage]) -> None:
with open(en_us_msgs, "r") as f:
existing_msg = json.loads(f.read())
for msg in messages:
if msg.kabab in existing_msg:
continue
existing_msg[msg.kabab] = msg.message
print(f"Added Key {msg.kabab} to 'en-US.json'")
with open(en_us_msgs, "w") as f:
f.write(json.dumps(existing_msg, indent=4))
def main():
print("Starting...")
GENERATE_MESSAGES.sort()
code_gen = code_gen_factory(server_side_msgs)
code_gen.purge_lines()
messages = []
for msg_type in GENERATE_MESSAGES:
messages += get_messages(msg_type)
messages.append("\n")
for msg in messages:
code_gen.push_line(msg)
with open(server_side_msgs, "w") as file:
file.writelines(code_gen.text)
# Locals
local_msgs = []
for msg_type in GENERATE_MESSAGES:
local_msgs += ErrorMessage.factory(msg_type)
write_to_locals(local_msgs)
print("Done!")
if __name__ == "__main__":
main()

View file

@ -1,32 +0,0 @@
import json
import requests
from pydantic import BaseModel
class GithubIssue(BaseModel):
url: str
number: int
title: str
def get_issues_by_label(label="fixed-pending-release") -> list[GithubIssue]:
issues_url = f"https://api.github.com/repos/hay-kot/mealie/issues?labels={label}"
response = requests.get(issues_url)
issues = json.loads(response.text)
return [GithubIssue(**issue) for issue in issues]
def format_markdown_list(issues: list[GithubIssue]) -> str:
return "\n".join(f"- [{issue.number}]({issue.url}) - {issue.title}" for issue in issues)
def main() -> None:
issues = get_issues_by_label()
print(format_markdown_list(issues))
if __name__ == "__main__":
main()

View file

@ -1,156 +0,0 @@
# This Content is Auto Generated for Pytest
class AppRoutes:
def __init__(self) -> None:
self.prefix = '/api'
self.about_events = "/api/about/events"
self.about_events_notifications = "/api/about/events/notifications"
self.about_events_notifications_test = "/api/about/events/notifications/test"
self.about_recipes_defaults = "/api/about/recipes/defaults"
self.auth_refresh = "/api/auth/refresh"
self.auth_token = "/api/auth/token"
self.auth_token_long = "/api/auth/token/long"
self.backups_available = "/api/backups/available"
self.backups_export_database = "/api/backups/export/database"
self.backups_upload = "/api/backups/upload"
self.categories = "/api/categories"
self.categories_empty = "/api/categories/empty"
self.debug = "/api/debug"
self.debug_last_recipe_json = "/api/debug/last-recipe-json"
self.debug_log = "/api/debug/log"
self.debug_statistics = "/api/debug/statistics"
self.debug_version = "/api/debug/version"
self.groups = "/api/groups"
self.groups_self = "/api/groups/self"
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.meal_plans_today_image = "/api/meal-plans/today/image"
self.migrations = "/api/migrations"
self.recipes = "/api/recipes"
self.recipes_category = "/api/recipes/category"
self.recipes_create = "/api/recipes/create"
self.recipes_create_from_zip = "/api/recipes/create-from-zip"
self.recipes_create_url = "/api/recipes/create-url"
self.recipes_summary_uncategorized = "/api/recipes/summary/uncategorized"
self.recipes_summary_untagged = "/api/recipes/summary/untagged"
self.recipes_tag = "/api/recipes/tag"
self.recipes_test_scrape_url = "/api/recipes/test-scrape-url"
self.shopping_lists = "/api/shopping-lists"
self.site_settings = "/api/site-settings"
self.site_settings_custom_pages = "/api/site-settings/custom-pages"
self.site_settings_webhooks_test = "/api/site-settings/webhooks/test"
self.tags = "/api/tags"
self.tags_empty = "/api/tags/empty"
self.themes = "/api/themes"
self.themes_create = "/api/themes/create"
self.users = "/api/users"
self.users_api_tokens = "/api/users/api-tokens"
self.users_self = "/api/users/self"
self.users_sign_ups = "/api/users/sign-ups"
self.utils_download = "/api/utils/download"
def about_events_id(self, id):
return f"{self.prefix}/about/events/{id}"
def about_events_notifications_id(self, id):
return f"{self.prefix}/about/events/notifications/{id}"
def backups_file_name_delete(self, file_name):
return f"{self.prefix}/backups/{file_name}/delete"
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 categories_category(self, category):
return f"{self.prefix}/categories/{category}"
def debug_log_num(self, num):
return f"{self.prefix}/debug/log/{num}"
def groups_id(self, id):
return f"{self.prefix}/groups/{id}"
def meal_plans_id(self, id):
return f"{self.prefix}/meal-plans/{id}"
def meal_plans_id_shopping_list(self, id):
return f"{self.prefix}/meal-plans/{id}/shopping-list"
def meal_plans_plan_id(self, plan_id):
return f"{self.prefix}/meal-plans/{plan_id}"
def media_recipes_recipe_slug_assets_file_name(self, recipe_slug, file_name):
return f"{self.prefix}/media/recipes/{recipe_slug}/assets/{file_name}"
def media_recipes_recipe_slug_images_file_name(self, recipe_slug, file_name):
return f"{self.prefix}/media/recipes/{recipe_slug}/images/{file_name}"
def migrations_import_type_file_name_delete(self, import_type, file_name):
return f"{self.prefix}/migrations/{import_type}/{file_name}/delete"
def migrations_import_type_file_name_import(self, import_type, file_name):
return f"{self.prefix}/migrations/{import_type}/{file_name}/import"
def migrations_import_type_upload(self, import_type):
return f"{self.prefix}/migrations/{import_type}/upload"
def recipes_recipe_slug(self, recipe_slug):
return f"{self.prefix}/recipes/{recipe_slug}"
def recipes_recipe_slug_assets(self, recipe_slug):
return f"{self.prefix}/recipes/{recipe_slug}/assets"
def recipes_recipe_slug_image(self, recipe_slug):
return f"{self.prefix}/recipes/{recipe_slug}/image"
def recipes_recipe_slug_zip(self, recipe_slug):
return f"{self.prefix}/recipes/{recipe_slug}/zip"
def recipes_slug_comments(self, slug):
return f"{self.prefix}/recipes/{slug}/comments"
def recipes_slug_comments_id(self, slug, id):
return f"{self.prefix}/recipes/{slug}/comments/{id}"
def shopping_lists_id(self, id):
return f"{self.prefix}/shopping-lists/{id}"
def site_settings_custom_pages_id(self, id):
return f"{self.prefix}/site-settings/custom-pages/{id}"
def tags_tag(self, tag):
return f"{self.prefix}/tags/{tag}"
def themes_id(self, id):
return f"{self.prefix}/themes/{id}"
def users_api_tokens_token_id(self, token_id):
return f"{self.prefix}/users/api-tokens/{token_id}"
def users_id(self, id):
return f"{self.prefix}/users/{id}"
def users_id_favorites(self, id):
return f"{self.prefix}/users/{id}/favorites"
def users_id_favorites_slug(self, id, slug):
return f"{self.prefix}/users/{id}/favorites/{slug}"
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 users_id_reset_password(self, id):
return f"{self.prefix}/users/{id}/reset-password"
def users_sign_ups_token(self, token):
return f"{self.prefix}/users/sign-ups/{token}"

View file

@ -1,11 +0,0 @@
git checkout dev
git merge --strategy=ours master # keep the content of this branch, but record a merge
git checkout master
git merge dev # fast-forward master up to the merge
## TODOs
# Create New Branch v0.x.x
# Push Branch Version to Github
# Create Pull Request

View file

@ -1,17 +0,0 @@
import { requests } from "../requests";
const prefix = '{{paths.prefix}}'
const routes = { {% for path in paths.static_paths %}
{{ path.router_camel }}: `${prefix}{{ path.route }}`,{% endfor %}
{% for path in paths.function_paths %}
{{path.router_camel}}: ({{path.var|join(", ")}}) => `${prefix}{{ path.js_route }}`,{% endfor %}
}
export const {{paths.export_name}}API = { {% for path in paths.all_paths %} {% for verb in path.http_verbs %}
{% if verb.js_docs %}/** {{ verb.js_docs }}
*/ {% endif %}
async {{ verb.summary_camel }}({{ verb.function_args() }}) {
return await requests.{{ verb.request_type.value }}(routes.{{ path.route_object.router_camel }}{% if path.route_object.is_function %}({{verb.path_params()}}){% endif %}, {{ verb.query_params() }} {{ verb.payload() }})
}, {% endfor %} {% endfor %}
}

View file

@ -1,7 +0,0 @@
{% for api in files.files %}
import { {{ api.camel }}API } from "./interfaces/{{ api.slug }}" {% endfor %}
export const api = {
{% for api in files.files %}
{{api.camel}}: {{api.camel}}API, {% endfor %}
}

View file

@ -1,12 +0,0 @@
# This Content is Auto Generated for Pytest
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 %}