mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 14:33:33 -07:00
tests/suite-overhall - 83% Coverage
* generate API docs with make file * documentation * code-gen scripts * type() to isinstance() * code-gen * fix flake8 problems * test refactor first pass * init config * added help, format, clean and lint * + flake8 developer dep * update docs * proper api imports * jsconfig * group tests * refactor settings to class for testing * fix env errors * change tool -> tools * code cleanup * sort imports * add tools test * lint Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
parent
98a9f2f9c3
commit
6a71161252
151 changed files with 1627 additions and 4471 deletions
6
.flake8
Normal file
6
.flake8
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[flake8]
|
||||||
|
ignore = [
|
||||||
|
E501 # Line Length - See Black Config in pyproject.toml
|
||||||
|
E722 # Bare Exception | Temporary
|
||||||
|
]
|
||||||
|
exclude = _all_models.py
|
44
dev/scripts/api_docs_gen.py
Normal file
44
dev/scripts/api_docs_gen.py
Normal file
|
@ -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 = """<!-- 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)
|
83
dev/scripts/app_routes_gen.py
Normal file
83
dev/scripts/app_routes_gen.py
Normal file
|
@ -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)
|
97
dev/scripts/output/app_routes.py
Normal file
97
dev/scripts/output/app_routes.py
Normal file
|
@ -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}"
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
|
@ -14,25 +14,46 @@ const backupURLs = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
/**
|
||||||
|
* Request all backups available on the server
|
||||||
|
* @returns {Array} List of Available Backups
|
||||||
|
*/
|
||||||
async requestAvailable() {
|
async requestAvailable() {
|
||||||
let response = await apiReq.get(backupURLs.available);
|
let response = await apiReq.get(backupURLs.available);
|
||||||
return response.data;
|
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) {
|
async import(fileName, data) {
|
||||||
let response = await apiReq.post(backupURLs.importBackup(fileName), data);
|
let response = await apiReq.post(backupURLs.importBackup(fileName), data);
|
||||||
store.dispatch("requestRecentRecipes");
|
store.dispatch("requestRecentRecipes");
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Removes a file from the server
|
||||||
|
* @param {string} fileName
|
||||||
|
*/
|
||||||
async delete(fileName) {
|
async delete(fileName) {
|
||||||
await apiReq.delete(backupURLs.deleteBackup(fileName));
|
await apiReq.delete(backupURLs.deleteBackup(fileName));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
async create(data) {
|
* Creates a backup on the serve given a set of options
|
||||||
let response = apiReq.post(backupURLs.createBackup, data);
|
* @param {object} data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async create(options) {
|
||||||
|
let response = apiReq.post(backupURLs.createBackup, options);
|
||||||
return response;
|
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) {
|
async download(fileName) {
|
||||||
let response = await apiReq.get(backupURLs.downloadBackup(fileName));
|
let response = await apiReq.get(backupURLs.downloadBackup(fileName));
|
||||||
return response.data;
|
return response.data;
|
||||||
|
|
|
@ -12,7 +12,10 @@ import signUps from "./signUps";
|
||||||
import groups from "./groups";
|
import groups from "./groups";
|
||||||
import siteSettings from "./siteSettings";
|
import siteSettings from "./siteSettings";
|
||||||
|
|
||||||
export default {
|
/**
|
||||||
|
* The main object namespace for interacting with the backend database
|
||||||
|
*/
|
||||||
|
export const api = {
|
||||||
recipes: recipe,
|
recipes: recipe,
|
||||||
siteSettings: siteSettings,
|
siteSettings: siteSettings,
|
||||||
backups: backup,
|
backups: backup,
|
||||||
|
|
|
@ -19,6 +19,11 @@ const recipeURLs = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
/**
|
||||||
|
* Create a Recipe by URL
|
||||||
|
* @param {string} recipeURL
|
||||||
|
* @returns {string} Recipe Slug
|
||||||
|
*/
|
||||||
async createByURL(recipeURL) {
|
async createByURL(recipeURL) {
|
||||||
let response = await apiReq.post(recipeURLs.createByURL, {
|
let response = await apiReq.post(recipeURLs.createByURL, {
|
||||||
url: recipeURL,
|
url: recipeURL,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { apiReq } from "./api-utils";
|
import { apiReq } from "./api-utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// import api from "@/api";
|
// import { api } from "@/api";
|
||||||
async uploadFile(url, fileObject) {
|
async uploadFile(url, fileObject) {
|
||||||
let response = await apiReq.post(url, fileObject, {
|
let response = await apiReq.post(url, fileObject, {
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
@ -106,7 +106,7 @@
|
||||||
import { validators } from "@/mixins/validators";
|
import { validators } from "@/mixins/validators";
|
||||||
import { initials } from "@/mixins/initials";
|
import { initials } from "@/mixins/initials";
|
||||||
import { user } from "@/mixins/user";
|
import { user } from "@/mixins/user";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
export default {
|
export default {
|
||||||
mixins: [validators, initials, user],
|
mixins: [validators, initials, user],
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ImportDialog from "./ImportDialog";
|
import ImportDialog from "./ImportDialog";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import utils from "@/utils";
|
import utils from "@/utils";
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ImportDialog from "./ImportDialog";
|
import ImportDialog from "./ImportDialog";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import utils from "@/utils";
|
import utils from "@/utils";
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ImportOptions from "@/components/Admin/Backup/ImportOptions";
|
import ImportOptions from "@/components/Admin/Backup/ImportOptions";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
export default {
|
export default {
|
||||||
components: { ImportOptions },
|
components: { ImportOptions },
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const NEW_PAGE_EVENT = "refresh-page";
|
const NEW_PAGE_EVENT = "refresh-page";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import CategorySelector from "@/components/FormHelpers/CategorySelector";
|
import CategorySelector from "@/components/FormHelpers/CategorySelector";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
<script>
|
<script>
|
||||||
import draggable from "vuedraggable";
|
import draggable from "vuedraggable";
|
||||||
import CreatePageDialog from "@/components/Admin/General/CreatePageDialog";
|
import CreatePageDialog from "@/components/Admin/General/CreatePageDialog";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
draggable,
|
draggable,
|
||||||
|
|
|
@ -129,7 +129,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import LanguageMenu from "@/components/UI/LanguageMenu";
|
import LanguageMenu from "@/components/UI/LanguageMenu";
|
||||||
import draggable from "vuedraggable";
|
import draggable from "vuedraggable";
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,11 @@
|
||||||
<Confirmation
|
<Confirmation
|
||||||
ref="deleteGroupConfirm"
|
ref="deleteGroupConfirm"
|
||||||
:title="$t('user.confirm-group-deletion')"
|
:title="$t('user.confirm-group-deletion')"
|
||||||
:message="$t('user.are-you-sure-you-want-to-delete-the-group', { groupName:group.name })"
|
:message="
|
||||||
|
$t('user.are-you-sure-you-want-to-delete-the-group', {
|
||||||
|
groupName: group.name,
|
||||||
|
})
|
||||||
|
"
|
||||||
icon="mdi-alert"
|
icon="mdi-alert"
|
||||||
@confirm="deleteGroup"
|
@confirm="deleteGroup"
|
||||||
:width="450"
|
:width="450"
|
||||||
|
@ -13,7 +17,9 @@
|
||||||
<v-list dense>
|
<v-list dense>
|
||||||
<v-card-title class="py-1">{{ group.name }}</v-card-title>
|
<v-card-title class="py-1">{{ group.name }}</v-card-title>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-subheader>{{ $t('user.group-id-with-value', { groupID: group.id }) }}</v-subheader>
|
<v-subheader>{{
|
||||||
|
$t("user.group-id-with-value", { groupID: group.id })
|
||||||
|
}}</v-subheader>
|
||||||
<v-list-item-group color="primary">
|
<v-list-item-group color="primary">
|
||||||
<v-list-item v-for="property in groupProps" :key="property.text">
|
<v-list-item v-for="property in groupProps" :key="property.text">
|
||||||
<v-list-item-icon>
|
<v-list-item-icon>
|
||||||
|
@ -36,11 +42,11 @@
|
||||||
@click="confirmDelete"
|
@click="confirmDelete"
|
||||||
:disabled="ableToDelete"
|
:disabled="ableToDelete"
|
||||||
>
|
>
|
||||||
{{ $t('general.delete') }}
|
{{ $t("general.delete") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<!-- Coming Soon! -->
|
<!-- Coming Soon! -->
|
||||||
<v-btn small color="success" disabled>
|
<v-btn small color="success" disabled>
|
||||||
{{ $t('general.edit') }}
|
{{ $t("general.edit") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
@ -50,7 +56,7 @@
|
||||||
<script>
|
<script>
|
||||||
const RENDER_EVENT = "update";
|
const RENDER_EVENT = "update";
|
||||||
import Confirmation from "@/components/UI/Confirmation";
|
import Confirmation from "@/components/UI/Confirmation";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
export default {
|
export default {
|
||||||
components: { Confirmation },
|
components: { Confirmation },
|
||||||
props: {
|
props: {
|
||||||
|
@ -94,22 +100,24 @@ export default {
|
||||||
buildData() {
|
buildData() {
|
||||||
this.groupProps = [
|
this.groupProps = [
|
||||||
{
|
{
|
||||||
text: this.$t('user.total-users'),
|
text: this.$t("user.total-users"),
|
||||||
icon: "mdi-account",
|
icon: "mdi-account",
|
||||||
value: this.group.users.length,
|
value: this.group.users.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: this.$t('user.total-mealplans'),
|
text: this.$t("user.total-mealplans"),
|
||||||
icon: "mdi-food",
|
icon: "mdi-food",
|
||||||
value: this.group.mealplans.length,
|
value: this.group.mealplans.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: this.$t('user.webhooks-enabled'),
|
text: this.$t("user.webhooks-enabled"),
|
||||||
icon: "mdi-webhook",
|
icon: "mdi-webhook",
|
||||||
value: this.group.webhookEnable ? this.$t('general.yes') : this.$t('general.no'),
|
value: this.group.webhookEnable
|
||||||
|
? this.$t("general.yes")
|
||||||
|
: this.$t("general.no"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: this.$t('user.webhook-time'),
|
text: this.$t("user.webhook-time"),
|
||||||
icon: "mdi-clock-outline",
|
icon: "mdi-clock-outline",
|
||||||
value: this.group.webhookTime,
|
value: this.group.webhookTime,
|
||||||
},
|
},
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { validators } from "@/mixins/validators";
|
import { validators } from "@/mixins/validators";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import GroupCard from "@/components/Admin/ManageUsers/GroupCard";
|
import GroupCard from "@/components/Admin/ManageUsers/GroupCard";
|
||||||
export default {
|
export default {
|
||||||
components: { GroupCard },
|
components: { GroupCard },
|
||||||
|
|
|
@ -111,7 +111,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Confirmation from "@/components/UI/Confirmation";
|
import Confirmation from "@/components/UI/Confirmation";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import { validators } from "@/mixins/validators";
|
import { validators } from "@/mixins/validators";
|
||||||
export default {
|
export default {
|
||||||
components: { Confirmation },
|
components: { Confirmation },
|
||||||
|
|
|
@ -145,7 +145,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Confirmation from "@/components/UI/Confirmation";
|
import Confirmation from "@/components/UI/Confirmation";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import { validators } from "@/mixins/validators";
|
import { validators } from "@/mixins/validators";
|
||||||
export default {
|
export default {
|
||||||
components: { Confirmation },
|
components: { Confirmation },
|
||||||
|
@ -274,7 +274,7 @@ export default {
|
||||||
},
|
},
|
||||||
resetPassword() {
|
resetPassword() {
|
||||||
console.log(this.activeId);
|
console.log(this.activeId);
|
||||||
api.users.resetPassword(this.editedItem.id );
|
api.users.resetPassword(this.editedItem.id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
<script>
|
<script>
|
||||||
import UploadBtn from "../../UI/UploadBtn";
|
import UploadBtn from "../../UI/UploadBtn";
|
||||||
import utils from "@/utils";
|
import utils from "@/utils";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
folder: String,
|
folder: String,
|
||||||
|
|
|
@ -10,7 +10,10 @@
|
||||||
/>
|
/>
|
||||||
<v-card flat outlined class="ma-2">
|
<v-card flat outlined class="ma-2">
|
||||||
<v-card-text class="mb-n5 mt-n2">
|
<v-card-text class="mb-n5 mt-n2">
|
||||||
<h3>{{ theme.name }} {{ current ? $t('general.current-parenthesis') : "" }}</h3>
|
<h3>
|
||||||
|
{{ theme.name }}
|
||||||
|
{{ current ? $t("general.current-parenthesis") : "" }}
|
||||||
|
</h3>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-row flex align-center>
|
<v-row flex align-center>
|
||||||
|
@ -27,10 +30,14 @@
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn text color="error" @click="confirmDelete"> {{$t('general.delete')}} </v-btn>
|
<v-btn text color="error" @click="confirmDelete">
|
||||||
|
{{ $t("general.delete") }}
|
||||||
|
</v-btn>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<!-- <v-btn text color="accent" @click="editTheme">Edit</v-btn> -->
|
<!-- <v-btn text color="accent" @click="editTheme">Edit</v-btn> -->
|
||||||
<v-btn text color="success" @click="saveThemes">{{$t('general.apply')}}</v-btn>
|
<v-btn text color="success" @click="saveThemes">{{
|
||||||
|
$t("general.apply")
|
||||||
|
}}</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,7 +45,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Confirmation from "@/components/UI/Confirmation";
|
import Confirmation from "@/components/UI/Confirmation";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
|
|
||||||
const DELETE_EVENT = "delete";
|
const DELETE_EVENT = "delete";
|
||||||
const APPLY_EVENT = "apply";
|
const APPLY_EVENT = "apply";
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import VJsoneditor from "v-jsoneditor";
|
import VJsoneditor from "v-jsoneditor";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
export default {
|
export default {
|
||||||
components: { VJsoneditor },
|
components: { VJsoneditor },
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
export default {
|
export default {
|
||||||
props: {},
|
props: {},
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -83,7 +83,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import { validators } from "@/mixins/validators";
|
import { validators } from "@/mixins/validators";
|
||||||
export default {
|
export default {
|
||||||
mixins: [validators],
|
mixins: [validators],
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import utils from "@/utils";
|
import utils from "@/utils";
|
||||||
import MealPlanCard from "./MealPlanCard";
|
import MealPlanCard from "./MealPlanCard";
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -83,7 +83,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const CREATE_EVENT = "created";
|
const CREATE_EVENT = "created";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import utils from "@/utils";
|
import utils from "@/utils";
|
||||||
import MealPlanCard from "./MealPlanCard";
|
import MealPlanCard from "./MealPlanCard";
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
const levenshtein = require("fast-levenshtein");
|
const levenshtein = require("fast-levenshtein");
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
ref="deleteRecipieConfirm"
|
ref="deleteRecipieConfirm"
|
||||||
v-on:confirm="deleteRecipe()"
|
v-on:confirm="deleteRecipe()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<v-btn class="mr-2" fab dark small color="success" @click="save">
|
<v-btn class="mr-2" fab dark small color="success" @click="save">
|
||||||
<v-icon>mdi-content-save</v-icon>
|
<v-icon>mdi-content-save</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
@ -37,7 +36,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Confirmation from "../../components/UI/Confirmation";
|
import Confirmation from "../../components/UI/Confirmation.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
@ -58,6 +57,7 @@ export default {
|
||||||
save() {
|
save() {
|
||||||
this.$emit("save");
|
this.$emit("save");
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteRecipeConfrim() {
|
deleteRecipeConfrim() {
|
||||||
this.$refs.deleteRecipieConfirm.open();
|
this.$refs.deleteRecipieConfirm.open();
|
||||||
},
|
},
|
||||||
|
|
|
@ -254,7 +254,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import draggable from "vuedraggable";
|
import draggable from "vuedraggable";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import utils from "@/utils";
|
import utils from "@/utils";
|
||||||
import BulkAdd from "./BulkAdd";
|
import BulkAdd from "./BulkAdd";
|
||||||
import ExtrasEditor from "./ExtrasEditor";
|
import ExtrasEditor from "./ExtrasEditor";
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const UPLOAD_EVENT = "uploaded";
|
const UPLOAD_EVENT = "uploaded";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
url: String,
|
url: String,
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import SuccessFailureAlert from "@/components/UI/SuccessFailureAlert";
|
import SuccessFailureAlert from "@/components/UI/SuccessFailureAlert";
|
||||||
import ImportSummaryDialog from "@/components/Admin/Backup/ImportSummaryDialog";
|
import ImportSummaryDialog from "@/components/Admin/Backup/ImportSummaryDialog";
|
||||||
import UploadBtn from "@/components/UI/UploadBtn";
|
import UploadBtn from "@/components/UI/UploadBtn";
|
||||||
|
|
|
@ -108,7 +108,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import TimePickerDialog from "@/components/Admin/MealPlanner/TimePickerDialog";
|
import TimePickerDialog from "@/components/Admin/MealPlanner/TimePickerDialog";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
<script>
|
<script>
|
||||||
import MigrationCard from "@/components/Admin/Migration/MigrationCard";
|
import MigrationCard from "@/components/Admin/Migration/MigrationCard";
|
||||||
import SuccessFailureAlert from "@/components/UI/SuccessFailureAlert";
|
import SuccessFailureAlert from "@/components/UI/SuccessFailureAlert";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
MigrationCard,
|
MigrationCard,
|
||||||
|
|
|
@ -13,9 +13,9 @@
|
||||||
>
|
>
|
||||||
</v-progress-circular>
|
</v-progress-circular>
|
||||||
</span>
|
</span>
|
||||||
{{$t('settings.profile')}}
|
{{ $t("settings.profile") }}
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
{{$t('user.user-id-with-value', {id: user.id }) }}
|
{{ $t("user.user-id-with-value", { id: user.id }) }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
<v-col cols="12" md="4" sm="12">
|
<v-col cols="12" md="4" sm="12">
|
||||||
<v-card height="100%">
|
<v-card height="100%">
|
||||||
<v-card-title class="headline">
|
<v-card-title class="headline">
|
||||||
{{$t('user.reset-password')}}
|
{{ $t("user.reset-password") }}
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
|
@ -114,7 +114,8 @@
|
||||||
prepend-icon="mdi-lock"
|
prepend-icon="mdi-lock"
|
||||||
:label="$t('user.confirm-password')"
|
:label="$t('user.confirm-password')"
|
||||||
:rules="[
|
:rules="[
|
||||||
password.newOne === password.newTwo || $t('user.password-must-match'),
|
password.newOne === password.newTwo ||
|
||||||
|
$t('user.password-must-match'),
|
||||||
]"
|
]"
|
||||||
validate-on-blur
|
validate-on-blur
|
||||||
:type="showPassword ? 'text' : 'password'"
|
:type="showPassword ? 'text' : 'password'"
|
||||||
|
@ -145,7 +146,7 @@
|
||||||
<script>
|
<script>
|
||||||
// import AvatarPicker from '@/components/AvatarPicker'
|
// import AvatarPicker from '@/components/AvatarPicker'
|
||||||
import UploadBtn from "@/components/UI/UploadBtn";
|
import UploadBtn from "@/components/UI/UploadBtn";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import { validators } from "@/mixins/validators";
|
import { validators } from "@/mixins/validators";
|
||||||
import { initials } from "@/mixins/initials";
|
import { initials } from "@/mixins/initials";
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -134,7 +134,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import ColorPickerDialog from "@/components/Admin/Theme/ColorPickerDialog";
|
import ColorPickerDialog from "@/components/Admin/Theme/ColorPickerDialog";
|
||||||
import NewThemeDialog from "@/components/Admin/Theme/NewThemeDialog";
|
import NewThemeDialog from "@/components/Admin/Theme/NewThemeDialog";
|
||||||
import ThemeCard from "@/components/Admin/Theme/ThemeCard";
|
import ThemeCard from "@/components/Admin/Theme/ThemeCard";
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import CardSection from "../components/UI/CardSection";
|
import CardSection from "../components/UI/CardSection";
|
||||||
import CategorySidebar from "../components/UI/CategorySidebar";
|
import CategorySidebar from "../components/UI/CategorySidebar";
|
||||||
export default {
|
export default {
|
||||||
|
@ -53,8 +53,8 @@ export default {
|
||||||
await this.$store.dispatch("requestSiteSettings");
|
await this.$store.dispatch("requestSiteSettings");
|
||||||
this.siteSettings.categories.forEach(async element => {
|
this.siteSettings.categories.forEach(async element => {
|
||||||
let recipes = await this.getRecipeByCategory(element.slug);
|
let recipes = await this.getRecipeByCategory(element.slug);
|
||||||
if (recipes.recipes.length < 0 ) recipes.recipes = []
|
if (recipes.recipes.length < 0) recipes.recipes = [];
|
||||||
console.log(recipes)
|
console.log(recipes);
|
||||||
this.recipeByCategory.push(recipes);
|
this.recipeByCategory.push(recipes);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -85,7 +85,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import utils from "@/utils";
|
import utils from "@/utils";
|
||||||
import NewMeal from "@/components/MealPlan/MealPlanNew";
|
import NewMeal from "@/components/MealPlan/MealPlanNew";
|
||||||
import EditPlan from "@/components/MealPlan/MealPlanEditor";
|
import EditPlan from "@/components/MealPlan/MealPlanEditor";
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import utils from "@/utils";
|
import utils from "@/utils";
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
|
|
||||||
import RecipeEditor from "@/components/Recipe/RecipeEditor";
|
import RecipeEditor from "@/components/Recipe/RecipeEditor";
|
||||||
import VJsoneditor from "v-jsoneditor";
|
import VJsoneditor from "v-jsoneditor";
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import utils from "@/utils";
|
import utils from "@/utils";
|
||||||
import VJsoneditor from "v-jsoneditor";
|
import VJsoneditor from "v-jsoneditor";
|
||||||
import RecipeViewer from "@/components/Recipe/RecipeViewer";
|
import RecipeViewer from "@/components/Recipe/RecipeViewer";
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import CardSection from "@/components/UI/CardSection";
|
import CardSection from "@/components/UI/CardSection";
|
||||||
import CategorySidebar from "@/components/UI/CategorySidebar";
|
import CategorySidebar from "@/components/UI/CategorySidebar";
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<script>
|
<script>
|
||||||
import CardSection from "@/components/UI/CardSection";
|
import CardSection from "@/components/UI/CardSection";
|
||||||
import CategorySidebar from "@/components/UI/CategorySidebar";
|
import CategorySidebar from "@/components/UI/CategorySidebar";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Debug from "@/pages/Debug";
|
||||||
import LoginPage from "@/pages/LoginPage";
|
import LoginPage from "@/pages/LoginPage";
|
||||||
import SignUpPage from "@/pages/SignUpPage";
|
import SignUpPage from "@/pages/SignUpPage";
|
||||||
import ThisWeek from "@/pages/MealPlan/ThisWeek";
|
import ThisWeek from "@/pages/MealPlan/ThisWeek";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import Admin from "./admin";
|
import Admin from "./admin";
|
||||||
import { store } from "../store";
|
import { store } from "../store";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import Vuex from "vuex";
|
import Vuex from "vuex";
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import createPersistedState from "vuex-persistedstate";
|
import createPersistedState from "vuex-persistedstate";
|
||||||
import userSettings from "./modules/userSettings";
|
import userSettings from "./modules/userSettings";
|
||||||
import language from "./modules/language";
|
import language from "./modules/language";
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
groups: [],
|
groups: [],
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
showRecent: true,
|
showRecent: true,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
siteSettings: {
|
siteSettings: {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import api from "@/api";
|
import { api } from "@/api";
|
||||||
import Vuetify from "../../plugins/vuetify";
|
import Vuetify from "../../plugins/vuetify";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
|
|
71
makefile
71
makefile
|
@ -1,25 +1,82 @@
|
||||||
setup:
|
define BROWSER_PYSCRIPT
|
||||||
|
import os, webbrowser, sys
|
||||||
|
|
||||||
|
from urllib.request import pathname2url
|
||||||
|
|
||||||
|
webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1])))
|
||||||
|
endef
|
||||||
|
export BROWSER_PYSCRIPT
|
||||||
|
|
||||||
|
define PRINT_HELP_PYSCRIPT
|
||||||
|
import re, sys
|
||||||
|
|
||||||
|
for line in sys.stdin:
|
||||||
|
match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
|
||||||
|
if match:
|
||||||
|
target, help = match.groups()
|
||||||
|
print("%-20s %s" % (target, help))
|
||||||
|
endef
|
||||||
|
export PRINT_HELP_PYSCRIPT
|
||||||
|
|
||||||
|
BROWSER := python -c "$$BROWSER_PYSCRIPT"
|
||||||
|
|
||||||
|
help:
|
||||||
|
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
|
||||||
|
|
||||||
|
clean: clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
|
||||||
|
|
||||||
|
clean-pyc: ## remove Python file artifacts
|
||||||
|
find . -name '*.pyc' -exec rm -f {} +
|
||||||
|
find . -name '*.pyo' -exec rm -f {} +
|
||||||
|
find . -name '*~' -exec rm -f {} +
|
||||||
|
find . -name '__pycache__' -exec rm -fr {} +
|
||||||
|
|
||||||
|
clean-test: ## remove test and coverage artifacts
|
||||||
|
rm -fr .tox/
|
||||||
|
rm -f .coverage
|
||||||
|
rm -fr htmlcov/
|
||||||
|
rm -fr .pytest_cache
|
||||||
|
|
||||||
|
test: ## run tests quickly with the default Python
|
||||||
|
poetry run pytest
|
||||||
|
|
||||||
|
format:
|
||||||
|
poetry run black .
|
||||||
|
|
||||||
|
lint: ## check style with flake8
|
||||||
|
poetry run flake8 mealie tests
|
||||||
|
|
||||||
|
setup: ## Setup Development Instance
|
||||||
poetry install && \
|
poetry install && \
|
||||||
cd frontend && \
|
cd frontend && \
|
||||||
npm install && \
|
npm install && \
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
backend:
|
backend: ## Start Mealie Backend Development Server
|
||||||
poetry run python mealie/db/init_db.py && \
|
poetry run python mealie/db/init_db.py && \
|
||||||
poetry run python mealie/app.py
|
poetry run python mealie/app.py
|
||||||
|
|
||||||
.PHONY: frontend
|
.PHONY: frontend
|
||||||
frontend:
|
frontend: ## Start Mealie Frontend Development Server
|
||||||
cd frontend && npm run serve
|
cd frontend && npm run serve
|
||||||
|
|
||||||
.PHONY: docs
|
.PHONY: docs
|
||||||
docs:
|
docs: ## Start Mkdocs Development Server
|
||||||
|
poetry run python dev/scripts/api_docs_gen.py && \
|
||||||
cd docs && poetry run python -m mkdocs serve
|
cd docs && poetry run python -m mkdocs serve
|
||||||
|
|
||||||
docker-dev:
|
docker-dev: ## Build and Start Docker Development Stack
|
||||||
docker-compose -f docker-compose.dev.yml -p dev-mealie up --build
|
docker-compose -f docker-compose.dev.yml -p dev-mealie up --build
|
||||||
|
|
||||||
docker-prod:
|
docker-prod: ## Build and Start Docker Production Stack
|
||||||
docker-compose -p mealie up --build -d
|
docker-compose -p mealie up --build -d
|
||||||
|
|
||||||
|
|
||||||
|
code-gen: ## Run Code-Gen Scripts
|
||||||
|
poetry run python dev/scripts/app_routes_gen.py
|
||||||
|
|
||||||
|
coverage: ## check code coverage quickly with the default Python
|
||||||
|
poetry run pytest
|
||||||
|
coverage report -m
|
||||||
|
coverage html
|
||||||
|
$(BROWSER) htmlcov/index.html
|
|
@ -3,25 +3,25 @@ from fastapi import FastAPI
|
||||||
from fastapi.logger import logger
|
from fastapi.logger import logger
|
||||||
|
|
||||||
# import utils.startup as startup
|
# import utils.startup as startup
|
||||||
from mealie.core.config import APP_VERSION, PORT, docs_url, redoc_url
|
from mealie.core.config import APP_VERSION, settings
|
||||||
from mealie.routes import backup_routes, debug_routes, migration_routes, theme_routes
|
from mealie.routes import backup_routes, debug_routes, migration_routes, theme_routes
|
||||||
from mealie.routes.site_settings import all_settings
|
|
||||||
from mealie.routes.groups import groups
|
from mealie.routes.groups import groups
|
||||||
from mealie.routes.mealplans import mealplans
|
from mealie.routes.mealplans import mealplans
|
||||||
from mealie.routes.recipe import all_recipe_routes, category_routes, recipe_crud_routes, tag_routes
|
from mealie.routes.recipe import all_recipe_routes, category_routes, recipe_crud_routes, tag_routes
|
||||||
|
from mealie.routes.site_settings import all_settings
|
||||||
from mealie.routes.users import users
|
from mealie.routes.users import users
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="Mealie",
|
title="Mealie",
|
||||||
description="A place for all your recipes",
|
description="A place for all your recipes",
|
||||||
version=APP_VERSION,
|
version=APP_VERSION,
|
||||||
docs_url=docs_url,
|
docs_url=settings.DOCS_URL,
|
||||||
redoc_url=redoc_url,
|
redoc_url=settings.REDOC_URL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def start_scheduler():
|
def start_scheduler():
|
||||||
import mealie.services.scheduler.scheduled_jobs
|
import mealie.services.scheduler.scheduled_jobs # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
def api_routers():
|
def api_routers():
|
||||||
|
@ -55,7 +55,7 @@ def main():
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
"app:app",
|
"app:app",
|
||||||
host="0.0.0.0",
|
host="0.0.0.0",
|
||||||
port=PORT,
|
port=settings.API_PORT,
|
||||||
reload=True,
|
reload=True,
|
||||||
reload_dirs=["mealie"],
|
reload_dirs=["mealie"],
|
||||||
debug=True,
|
debug=True,
|
||||||
|
|
|
@ -8,104 +8,101 @@ APP_VERSION = "v0.4.0"
|
||||||
DB_VERSION = "v0.4.0"
|
DB_VERSION = "v0.4.0"
|
||||||
|
|
||||||
CWD = Path(__file__).parent
|
CWD = Path(__file__).parent
|
||||||
|
BASE_DIR = CWD.parent.parent
|
||||||
|
|
||||||
|
ENV = BASE_DIR.joinpath(".env")
|
||||||
def ensure_dirs():
|
|
||||||
for dir in REQUIRED_DIRS:
|
|
||||||
dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
|
|
||||||
# Register ENV
|
|
||||||
ENV = CWD.joinpath(".env") #! I'm Broken Fix Me!
|
|
||||||
dotenv.load_dotenv(ENV)
|
dotenv.load_dotenv(ENV)
|
||||||
PRODUCTION = os.environ.get("ENV")
|
PRODUCTION = os.environ.get("ENV")
|
||||||
|
|
||||||
|
|
||||||
# General
|
def determine_data_dir(production: bool) -> Path:
|
||||||
PORT = int(os.getenv("mealie_port", 9000))
|
global CWD
|
||||||
API = os.getenv("api_docs", True)
|
if production:
|
||||||
|
return Path("/app/data")
|
||||||
|
|
||||||
if API:
|
return CWD.parent.parent.joinpath("dev", "data")
|
||||||
docs_url = "/docs"
|
|
||||||
redoc_url = "/redoc"
|
|
||||||
else:
|
|
||||||
docs_url = None
|
|
||||||
redoc_url = None
|
|
||||||
|
|
||||||
# Helpful Globals
|
|
||||||
DATA_DIR = CWD.parent.parent.joinpath("dev", "data")
|
|
||||||
if PRODUCTION:
|
|
||||||
DATA_DIR = Path("/app/data")
|
|
||||||
|
|
||||||
WEB_PATH = CWD.joinpath("dist")
|
|
||||||
IMG_DIR = DATA_DIR.joinpath("img")
|
|
||||||
BACKUP_DIR = DATA_DIR.joinpath("backups")
|
|
||||||
DEBUG_DIR = DATA_DIR.joinpath("debug")
|
|
||||||
MIGRATION_DIR = DATA_DIR.joinpath("migration")
|
|
||||||
NEXTCLOUD_DIR = MIGRATION_DIR.joinpath("nextcloud")
|
|
||||||
CHOWDOWN_DIR = MIGRATION_DIR.joinpath("chowdown")
|
|
||||||
TEMPLATE_DIR = DATA_DIR.joinpath("templates")
|
|
||||||
USER_DIR = DATA_DIR.joinpath("users")
|
|
||||||
SQLITE_DIR = DATA_DIR.joinpath("db")
|
|
||||||
RECIPE_DATA_DIR = DATA_DIR.joinpath("recipes")
|
|
||||||
TEMP_DIR = DATA_DIR.joinpath(".temp")
|
|
||||||
|
|
||||||
REQUIRED_DIRS = [
|
|
||||||
DATA_DIR,
|
|
||||||
IMG_DIR,
|
|
||||||
BACKUP_DIR,
|
|
||||||
DEBUG_DIR,
|
|
||||||
MIGRATION_DIR,
|
|
||||||
TEMPLATE_DIR,
|
|
||||||
SQLITE_DIR,
|
|
||||||
NEXTCLOUD_DIR,
|
|
||||||
CHOWDOWN_DIR,
|
|
||||||
RECIPE_DATA_DIR,
|
|
||||||
USER_DIR,
|
|
||||||
]
|
|
||||||
|
|
||||||
ensure_dirs()
|
|
||||||
|
|
||||||
LOGGER_FILE = DATA_DIR.joinpath("mealie.log")
|
|
||||||
|
|
||||||
|
|
||||||
# DATABASE ENV
|
def determine_secrets(data_dir: Path, production: bool) -> str:
|
||||||
SQLITE_FILE = None
|
if not production:
|
||||||
DATABASE_TYPE = os.getenv("DB_TYPE", "sqlite")
|
|
||||||
if DATABASE_TYPE == "sqlite":
|
|
||||||
USE_SQL = True
|
|
||||||
SQLITE_FILE = SQLITE_DIR.joinpath(f"mealie_{DB_VERSION}.sqlite")
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise Exception("Unable to determine database type. Acceptible options are 'sqlite' ")
|
|
||||||
|
|
||||||
|
|
||||||
def determine_secrets() -> str:
|
|
||||||
if not PRODUCTION:
|
|
||||||
return "shh-secret-test-key"
|
return "shh-secret-test-key"
|
||||||
|
|
||||||
secrets_file = DATA_DIR.joinpath(".secret")
|
secrets_file = data_dir.joinpath(".secret")
|
||||||
if secrets_file.is_file():
|
if secrets_file.is_file():
|
||||||
with open(secrets_file, "r") as f:
|
with open(secrets_file, "r") as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
else:
|
else:
|
||||||
with open(secrets_file, "w") as f:
|
with open(secrets_file, "w") as f:
|
||||||
f.write(secrets.token_hex(32))
|
new_secret = secrets.token_hex(32)
|
||||||
|
f.write(new_secret)
|
||||||
|
return new_secret
|
||||||
|
|
||||||
|
|
||||||
SECRET = "determine_secrets()"
|
class AppDirectories:
|
||||||
|
def __init__(self, cwd, data_dir) -> None:
|
||||||
|
self.DATA_DIR = data_dir
|
||||||
|
self.WEB_PATH = cwd.joinpath("dist")
|
||||||
|
self.IMG_DIR = data_dir.joinpath("img")
|
||||||
|
self.BACKUP_DIR = data_dir.joinpath("backups")
|
||||||
|
self.DEBUG_DIR = data_dir.joinpath("debug")
|
||||||
|
self.MIGRATION_DIR = data_dir.joinpath("migration")
|
||||||
|
self.NEXTCLOUD_DIR = self.MIGRATION_DIR.joinpath("nextcloud")
|
||||||
|
self.CHOWDOWN_DIR = self.MIGRATION_DIR.joinpath("chowdown")
|
||||||
|
self.TEMPLATE_DIR = data_dir.joinpath("templates")
|
||||||
|
self.USER_DIR = data_dir.joinpath("users")
|
||||||
|
self.SQLITE_DIR = data_dir.joinpath("db")
|
||||||
|
self.RECIPE_DATA_DIR = data_dir.joinpath("recipes")
|
||||||
|
self.TEMP_DIR = data_dir.joinpath(".temp")
|
||||||
|
|
||||||
# Mongo Database
|
self.ensure_directories()
|
||||||
DEFAULT_GROUP = os.getenv("DEFAULT_GROUP", "Home")
|
|
||||||
DEFAULT_PASSWORD = os.getenv("DEFAULT_PASSWORD", "MyPassword")
|
|
||||||
|
|
||||||
# Database
|
def ensure_directories(self):
|
||||||
MEALIE_DB_NAME = os.getenv("mealie_db_name", "mealie")
|
required_dirs = [
|
||||||
DB_USERNAME = os.getenv("db_username", "root")
|
self.IMG_DIR,
|
||||||
DB_PASSWORD = os.getenv("db_password", "example")
|
self.BACKUP_DIR,
|
||||||
DB_HOST = os.getenv("db_host", "mongo")
|
self.DEBUG_DIR,
|
||||||
DB_PORT = os.getenv("db_port", 27017)
|
self.MIGRATION_DIR,
|
||||||
|
self.TEMPLATE_DIR,
|
||||||
|
self.SQLITE_DIR,
|
||||||
|
self.NEXTCLOUD_DIR,
|
||||||
|
self.CHOWDOWN_DIR,
|
||||||
|
self.RECIPE_DATA_DIR,
|
||||||
|
self.USER_DIR,
|
||||||
|
]
|
||||||
|
|
||||||
# SFTP Email Stuff - For use Later down the line!
|
for dir in required_dirs:
|
||||||
SFTP_USERNAME = os.getenv("sftp_username", None)
|
dir.mkdir(parents=True, exist_ok=True)
|
||||||
SFTP_PASSWORD = os.getenv("sftp_password", None)
|
|
||||||
|
|
||||||
|
class AppSettings:
|
||||||
|
def __init__(self, app_dirs: AppDirectories) -> None:
|
||||||
|
global DB_VERSION
|
||||||
|
self.PRODUCTION = bool(os.environ.get("ENV"))
|
||||||
|
self.API_PORT = int(os.getenv("API_PORT", 9000))
|
||||||
|
self.API = os.getenv("API_DOCS", "False") == "True"
|
||||||
|
self.DOCS_URL = "/docs" if self.API else None
|
||||||
|
self.REDOC_URL = "/redoc" if self.API else None
|
||||||
|
self.SECRET = determine_secrets(app_dirs.DATA_DIR, self.PRODUCTION)
|
||||||
|
self.DATABASE_TYPE = os.getenv("DB_TYPE", "sqlite")
|
||||||
|
|
||||||
|
# Used to Set SQLite File Version
|
||||||
|
self.SQLITE_FILE = None
|
||||||
|
if self.DATABASE_TYPE == "sqlite":
|
||||||
|
self.SQLITE_FILE = app_dirs.SQLITE_DIR.joinpath(f"mealie_{DB_VERSION}.sqlite")
|
||||||
|
else:
|
||||||
|
raise Exception("Unable to determine database type. Acceptible options are 'sqlite'")
|
||||||
|
|
||||||
|
self.DEFAULT_GROUP = os.getenv("DEFAULT_GROUP", "Home")
|
||||||
|
self.DEFAULT_PASSWORD = os.getenv("DEFAULT_PASSWORD", "MyPassword")
|
||||||
|
|
||||||
|
# Not Used!
|
||||||
|
self.SFTP_USERNAME = os.getenv("SFTP_USERNAME", None)
|
||||||
|
self.SFTP_PASSWORD = os.getenv("SFTP_PASSWORD", None)
|
||||||
|
|
||||||
|
|
||||||
|
# General
|
||||||
|
DATA_DIR = determine_data_dir(PRODUCTION)
|
||||||
|
LOGGER_FILE = DATA_DIR.joinpath("mealie.log")
|
||||||
|
|
||||||
|
app_dirs = AppDirectories(CWD, DATA_DIR)
|
||||||
|
settings = AppSettings(app_dirs)
|
||||||
|
|
|
@ -2,7 +2,7 @@ from datetime import datetime, timedelta
|
||||||
from mealie.schema.user import UserInDB
|
from mealie.schema.user import UserInDB
|
||||||
|
|
||||||
from jose import jwt
|
from jose import jwt
|
||||||
from mealie.core.config import SECRET
|
from mealie.core.config import settings
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
from passlib.context import CryptContext
|
from passlib.context import CryptContext
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ def create_access_token(data: dict(), expires_delta: timedelta = None) -> str:
|
||||||
else:
|
else:
|
||||||
expire = datetime.utcnow() + timedelta(minutes=120)
|
expire = datetime.utcnow() + timedelta(minutes=120)
|
||||||
to_encode.update({"exp": expire})
|
to_encode.update({"exp": expire})
|
||||||
return jwt.encode(to_encode, SECRET, algorithm=ALGORITHM)
|
return jwt.encode(to_encode, settings.SECRET, algorithm=ALGORITHM)
|
||||||
|
|
||||||
|
|
||||||
def authenticate_user(session, email: str, password: str) -> UserInDB:
|
def authenticate_user(session, email: str, password: str) -> UserInDB:
|
||||||
|
|
|
@ -118,6 +118,7 @@ class _SignUps(BaseDocument):
|
||||||
self.orm_mode = True
|
self.orm_mode = True
|
||||||
self.schema = SignUpOut
|
self.schema = SignUpOut
|
||||||
|
|
||||||
|
|
||||||
class _CustomPages(BaseDocument):
|
class _CustomPages(BaseDocument):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.primary_key = "id"
|
self.primary_key = "id"
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
from mealie.db.models.model_base import SqlAlchemyBase
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from sqlalchemy.orm import load_only
|
from sqlalchemy.orm import load_only
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
from mealie.db.models.model_base import SqlAlchemyBase
|
|
||||||
|
|
||||||
|
|
||||||
class BaseDocument:
|
class BaseDocument:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
@ -21,12 +20,12 @@ class BaseDocument:
|
||||||
if self.orm_mode:
|
if self.orm_mode:
|
||||||
return [self.schema.from_orm(x) for x in session.query(self.sql_model).limit(limit).all()]
|
return [self.schema.from_orm(x) for x in session.query(self.sql_model).limit(limit).all()]
|
||||||
|
|
||||||
list = [x.dict() for x in session.query(self.sql_model).limit(limit).all()]
|
# list = [x.dict() for x in session.query(self.sql_model).limit(limit).all()]
|
||||||
|
|
||||||
if limit == 1:
|
# if limit == 1:
|
||||||
return list[0]
|
# return list[0]
|
||||||
|
|
||||||
return list
|
# return list
|
||||||
|
|
||||||
def get_all_limit_columns(self, session: Session, fields: List[str], limit: int = None) -> List[SqlAlchemyBase]:
|
def get_all_limit_columns(self, session: Session, fields: List[str], limit: int = None) -> List[SqlAlchemyBase]:
|
||||||
"""Queries the database for the selected model. Restricts return responses to the
|
"""Queries the database for the selected model. Restricts return responses to the
|
||||||
|
@ -40,12 +39,7 @@ class BaseDocument:
|
||||||
Returns:
|
Returns:
|
||||||
list[SqlAlchemyBase]: Returns a list of ORM objects
|
list[SqlAlchemyBase]: Returns a list of ORM objects
|
||||||
"""
|
"""
|
||||||
return (
|
return session.query(self.sql_model).options(load_only(*fields)).limit(limit).all()
|
||||||
session.query(self.sql_model)
|
|
||||||
.options(load_only(*fields))
|
|
||||||
.limit(limit)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_all_primary_keys(self, session: Session) -> List[str]:
|
def get_all_primary_keys(self, session: Session) -> List[str]:
|
||||||
"""Queries the database of the selected model and returns a list
|
"""Queries the database of the selected model and returns a list
|
||||||
|
@ -75,11 +69,7 @@ class BaseDocument:
|
||||||
if match_key is None:
|
if match_key is None:
|
||||||
match_key = self.primary_key
|
match_key = self.primary_key
|
||||||
|
|
||||||
return (
|
return session.query(self.sql_model).filter_by(**{match_key: match_value}).one()
|
||||||
session.query(self.sql_model)
|
|
||||||
.filter_by(**{match_key: match_value})
|
|
||||||
.one()
|
|
||||||
)
|
|
||||||
|
|
||||||
def get(self, session: Session, match_value: str, match_key: str = None, limit=1) -> BaseModel or List[BaseModel]:
|
def get(self, session: Session, match_value: str, match_key: str = None, limit=1) -> BaseModel or List[BaseModel]:
|
||||||
"""Retrieves an entry from the database by matching a key/value pair. If no
|
"""Retrieves an entry from the database by matching a key/value pair. If no
|
||||||
|
@ -120,14 +110,10 @@ class BaseDocument:
|
||||||
session.add(new_document)
|
session.add(new_document)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
if self.orm_mode:
|
return self.schema.from_orm(new_document)
|
||||||
return self.schema.from_orm(new_document)
|
|
||||||
|
|
||||||
return new_document.dict()
|
|
||||||
|
|
||||||
def update(self, session: Session, match_value: str, new_data: str) -> BaseModel:
|
def update(self, session: Session, match_value: str, new_data: str) -> BaseModel:
|
||||||
"""Update a database entry.
|
"""Update a database entry.
|
||||||
|
|
||||||
Args: \n
|
Args: \n
|
||||||
session (Session): Database Session
|
session (Session): Database Session
|
||||||
match_value (str): Match "key"
|
match_value (str): Match "key"
|
||||||
|
@ -140,13 +126,8 @@ class BaseDocument:
|
||||||
entry = self._query_one(session=session, match_value=match_value)
|
entry = self._query_one(session=session, match_value=match_value)
|
||||||
entry.update(session=session, **new_data)
|
entry.update(session=session, **new_data)
|
||||||
|
|
||||||
if self.orm_mode:
|
|
||||||
session.commit()
|
|
||||||
return self.schema.from_orm(entry)
|
|
||||||
|
|
||||||
return_data = entry.dict()
|
|
||||||
session.commit()
|
session.commit()
|
||||||
return return_data
|
return self.schema.from_orm(entry)
|
||||||
|
|
||||||
def delete(self, session: Session, primary_key_value) -> dict:
|
def delete(self, session: Session, primary_key_value) -> dict:
|
||||||
result = session.query(self.sql_model).filter_by(**{self.primary_key: primary_key_value}).one()
|
result = session.query(self.sql_model).filter_by(**{self.primary_key: primary_key_value}).one()
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
from mealie.core.config import SQLITE_FILE, USE_SQL
|
from mealie.core.config import settings
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
from mealie.db.models.db_session import sql_global_init
|
from mealie.db.models.db_session import sql_global_init
|
||||||
|
|
||||||
sql_exists = True
|
sql_exists = True
|
||||||
|
|
||||||
if USE_SQL:
|
sql_exists = settings.SQLITE_FILE.is_file()
|
||||||
sql_exists = SQLITE_FILE.is_file()
|
SessionLocal = sql_global_init(settings.SQLITE_FILE)
|
||||||
SessionLocal = sql_global_init(SQLITE_FILE)
|
|
||||||
else:
|
|
||||||
raise Exception("Cannot identify database type")
|
|
||||||
|
|
||||||
|
|
||||||
def create_session() -> Session:
|
def create_session() -> Session:
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
from fastapi.logger import logger
|
from fastapi.logger import logger
|
||||||
from mealie.core.config import DEFAULT_GROUP, DEFAULT_PASSWORD
|
from mealie.core.config import settings
|
||||||
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 create_session, sql_exists
|
from mealie.db.db_setup import create_session, sql_exists
|
||||||
from mealie.schema.settings import SiteSettings
|
from mealie.schema.settings import SiteSettings
|
||||||
from mealie.schema.theme import SiteTheme
|
from mealie.schema.theme import SiteTheme
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy.orm.session import Session
|
|
||||||
|
|
||||||
|
|
||||||
def init_db(db: Session = None) -> None:
|
def init_db(db: Session = None) -> None:
|
||||||
|
@ -24,20 +23,14 @@ def init_db(db: Session = None) -> None:
|
||||||
def default_theme_init(session: Session):
|
def default_theme_init(session: Session):
|
||||||
db.themes.create(session, SiteTheme().dict())
|
db.themes.create(session, SiteTheme().dict())
|
||||||
|
|
||||||
try:
|
|
||||||
logger.info("Generating default theme...")
|
|
||||||
except:
|
|
||||||
logger.info("Default Theme Exists.. skipping generation")
|
|
||||||
|
|
||||||
|
|
||||||
def default_settings_init(session: Session):
|
def default_settings_init(session: Session):
|
||||||
data = {"language": "en", "home_page_settings": {"categories": []}}
|
|
||||||
document = db.settings.create(session, SiteSettings().dict())
|
document = db.settings.create(session, SiteSettings().dict())
|
||||||
logger.info(f"Created Site Settings: \n {document}")
|
logger.info(f"Created Site Settings: \n {document}")
|
||||||
|
|
||||||
|
|
||||||
def default_group_init(session: Session):
|
def default_group_init(session: Session):
|
||||||
default_group = {"name": DEFAULT_GROUP}
|
default_group = {"name": settings.DEFAULT_GROUP}
|
||||||
logger.info("Generating Default Group")
|
logger.info("Generating Default Group")
|
||||||
db.groups.create(session, default_group)
|
db.groups.create(session, default_group)
|
||||||
|
|
||||||
|
@ -46,8 +39,8 @@ def default_user_init(session: Session):
|
||||||
default_user = {
|
default_user = {
|
||||||
"full_name": "Change Me",
|
"full_name": "Change Me",
|
||||||
"email": "changeme@email.com",
|
"email": "changeme@email.com",
|
||||||
"password": get_password_hash(DEFAULT_PASSWORD),
|
"password": get_password_hash(settings.DEFAULT_PASSWORD),
|
||||||
"group": DEFAULT_GROUP,
|
"group": settings.DEFAULT_GROUP,
|
||||||
"admin": True,
|
"admin": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ def sql_global_init(db_file: Path, check_thread=False):
|
||||||
|
|
||||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
import mealie.db.models._all_models
|
import mealie.db.models._all_models # noqa: F401
|
||||||
|
|
||||||
SqlAlchemyBase.metadata.create_all(engine)
|
SqlAlchemyBase.metadata.create_all(engine)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
import sqlalchemy.orm as orm
|
import sqlalchemy.orm as orm
|
||||||
from fastapi.logger import logger
|
|
||||||
from mealie.core.config import DEFAULT_GROUP
|
|
||||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||||
from mealie.db.models.recipe.category import Category, group2categories
|
from mealie.db.models.recipe.category import Category, group2categories
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
@ -61,17 +59,3 @@ class Group(SqlAlchemyBase, BaseMixins):
|
||||||
if item is None:
|
if item is None:
|
||||||
item = session.query(Group).filter(Group.id == 1).one()
|
item = session.query(Group).filter(Group.id == 1).one()
|
||||||
return item
|
return item
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_if_not_exist(session, name: str = DEFAULT_GROUP):
|
|
||||||
try:
|
|
||||||
result = session.query(Group).filter(Group.name == name).one()
|
|
||||||
if result:
|
|
||||||
logger.info("Category exists, associating recipe")
|
|
||||||
return result
|
|
||||||
else:
|
|
||||||
logger.info("Category doesn't exists, creating tag")
|
|
||||||
return Group(name=name)
|
|
||||||
except:
|
|
||||||
logger.info("Category doesn't exists, creating category")
|
|
||||||
return Group(name=name)
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ class Meal(SqlAlchemyBase):
|
||||||
|
|
||||||
class MealPlanModel(SqlAlchemyBase, BaseMixins):
|
class MealPlanModel(SqlAlchemyBase, BaseMixins):
|
||||||
__tablename__ = "mealplan"
|
__tablename__ = "mealplan"
|
||||||
uid = sa.Column(sa.Integer, primary_key=True, unique=True) #! Probably Bad?
|
uid = sa.Column(sa.Integer, primary_key=True, unique=True) # ! Probably Bad?
|
||||||
startDate = sa.Column(sa.Date)
|
startDate = sa.Column(sa.Date)
|
||||||
endDate = sa.Column(sa.Date)
|
endDate = sa.Column(sa.Date)
|
||||||
meals: List[Meal] = orm.relationship(Meal, cascade="all, delete, delete-orphan")
|
meals: List[Meal] = orm.relationship(Meal, cascade="all, delete, delete-orphan")
|
||||||
|
|
|
@ -1,47 +1,8 @@
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import sqlalchemy.ext.declarative as dec
|
import sqlalchemy.ext.declarative as dec
|
||||||
from sqlalchemy.orm.session import Session
|
|
||||||
|
|
||||||
SqlAlchemyBase = dec.declarative_base()
|
SqlAlchemyBase = dec.declarative_base()
|
||||||
|
|
||||||
|
|
||||||
class BaseMixins:
|
class BaseMixins:
|
||||||
@staticmethod
|
def _pass_on_me():
|
||||||
def _sql_remove_list(session: Session, list_of_tables: list, parent_id):
|
pass
|
||||||
for table in list_of_tables:
|
|
||||||
session.query(table).filter(parent_id == parent_id).delete()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _flatten_dict(list_of_dict: List[dict]):
|
|
||||||
finalMap = {}
|
|
||||||
for d in list_of_dict:
|
|
||||||
|
|
||||||
finalMap.update(d.dict())
|
|
||||||
|
|
||||||
return finalMap
|
|
||||||
|
|
||||||
|
|
||||||
# ! Don't use!
|
|
||||||
def update_generics(func):
|
|
||||||
"""An experimental function that does the initial work of updating attributes on a class
|
|
||||||
and passing "complex" data types recuresively to an "self.update()" function if one exists.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
func ([type]): [description]
|
|
||||||
"""
|
|
||||||
|
|
||||||
def wrapper(class_object, session, new_data: dict):
|
|
||||||
complex_attributed = {}
|
|
||||||
for key, value in new_data.items():
|
|
||||||
|
|
||||||
attribute = getattr(class_object, key, None)
|
|
||||||
|
|
||||||
if attribute and isinstance(attribute, SqlAlchemyBase):
|
|
||||||
attribute.update(session, value)
|
|
||||||
|
|
||||||
elif attribute:
|
|
||||||
setattr(class_object, key, value)
|
|
||||||
func(class_object, complex_attributed)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from datetime import date
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from mealie.db.models.model_base import SqlAlchemyBase
|
from mealie.db.models.model_base import SqlAlchemyBase
|
||||||
|
|
||||||
|
|
|
@ -57,14 +57,10 @@ class Category(SqlAlchemyBase):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_if_not_exist(session, name: str = None):
|
def create_if_not_exist(session, name: str = None):
|
||||||
test_slug = slugify(name)
|
test_slug = slugify(name)
|
||||||
try:
|
result = session.query(Category).filter(Category.slug == test_slug).one_or_none()
|
||||||
result = session.query(Category).filter(Category.slug == test_slug).one()
|
if result:
|
||||||
if result:
|
logger.info("Category exists, associating recipe")
|
||||||
logger.info("Category exists, associating recipe")
|
return result
|
||||||
return result
|
else:
|
||||||
else:
|
logger.info("Category doesn't exists, creating tag")
|
||||||
logger.info("Category doesn't exists, creating tag")
|
|
||||||
return Category(name=name)
|
|
||||||
except:
|
|
||||||
logger.info("Category doesn't exists, creating category")
|
|
||||||
return Category(name=name)
|
return Category(name=name)
|
||||||
|
|
|
@ -32,7 +32,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
cookTime = sa.Column(sa.String)
|
cookTime = sa.Column(sa.String)
|
||||||
recipeYield = sa.Column(sa.String)
|
recipeYield = sa.Column(sa.String)
|
||||||
recipeCuisine = sa.Column(sa.String)
|
recipeCuisine = sa.Column(sa.String)
|
||||||
tool: List[Tool] = orm.relationship("Tool", cascade="all, delete-orphan")
|
tools: List[Tool] = orm.relationship("Tool", cascade="all, delete-orphan")
|
||||||
nutrition: Nutrition = orm.relationship("Nutrition", uselist=False, cascade="all, delete-orphan")
|
nutrition: Nutrition = orm.relationship("Nutrition", uselist=False, cascade="all, delete-orphan")
|
||||||
recipeCategory: List = orm.relationship("Category", secondary=recipes2categories, back_populates="recipes")
|
recipeCategory: List = orm.relationship("Category", secondary=recipes2categories, back_populates="recipes")
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
totalTime: str = None,
|
totalTime: str = None,
|
||||||
prepTime: str = None,
|
prepTime: str = None,
|
||||||
nutrition: dict = None,
|
nutrition: dict = None,
|
||||||
tool: list[str] = [],
|
tools: list[str] = [],
|
||||||
performTime: str = None,
|
performTime: str = None,
|
||||||
slug: str = None,
|
slug: str = None,
|
||||||
recipeCategory: List[str] = None,
|
recipeCategory: List[str] = None,
|
||||||
|
@ -97,7 +97,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
else:
|
else:
|
||||||
self.nutrition = Nutrition()
|
self.nutrition = Nutrition()
|
||||||
|
|
||||||
self.tool = [Tool(tool=x) for x in tool] if tool else []
|
self.tools = [Tool(tool=x) for x in tools] if tools else []
|
||||||
|
|
||||||
self.recipeYield = recipeYield
|
self.recipeYield = recipeYield
|
||||||
self.recipeIngredient = [RecipeIngredient(ingredient=ingr) for ingr in recipeIngredient]
|
self.recipeIngredient = [RecipeIngredient(ingredient=ingr) for ingr in recipeIngredient]
|
||||||
|
@ -131,7 +131,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
recipeInstructions: List[dict] = None,
|
recipeInstructions: List[dict] = None,
|
||||||
recipeCuisine: str = None,
|
recipeCuisine: str = None,
|
||||||
totalTime: str = None,
|
totalTime: str = None,
|
||||||
tool: list[str] = [],
|
tools: list[str] = [],
|
||||||
prepTime: str = None,
|
prepTime: str = None,
|
||||||
performTime: str = None,
|
performTime: str = None,
|
||||||
nutrition: dict = None,
|
nutrition: dict = None,
|
||||||
|
@ -159,7 +159,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
prepTime=prepTime,
|
prepTime=prepTime,
|
||||||
performTime=performTime,
|
performTime=performTime,
|
||||||
nutrition=nutrition,
|
nutrition=nutrition,
|
||||||
tool=tool,
|
tools=tools,
|
||||||
slug=slug,
|
slug=slug,
|
||||||
recipeCategory=recipeCategory,
|
recipeCategory=recipeCategory,
|
||||||
tags=tags,
|
tags=tags,
|
||||||
|
|
|
@ -22,7 +22,7 @@ class Tag(SqlAlchemyBase):
|
||||||
|
|
||||||
@validates("name")
|
@validates("name")
|
||||||
def validate_name(self, key, name):
|
def validate_name(self, key, name):
|
||||||
assert not name == ""
|
assert name != ""
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def __init__(self, name) -> None:
|
def __init__(self, name) -> None:
|
||||||
|
@ -32,16 +32,11 @@ class Tag(SqlAlchemyBase):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_if_not_exist(session, name: str = None):
|
def create_if_not_exist(session, name: str = None):
|
||||||
test_slug = slugify(name)
|
test_slug = slugify(name)
|
||||||
try:
|
result = session.query(Tag).filter(Tag.slug == test_slug).one_or_none()
|
||||||
result = session.query(Tag).filter(Tag.slug == test_slug).first()
|
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
logger.info("Tag exists, associating recipe")
|
logger.info("Tag exists, associating recipe")
|
||||||
|
return result
|
||||||
return result
|
else:
|
||||||
else:
|
|
||||||
logger.info("Tag doesn't exists, creating tag")
|
|
||||||
return Tag(name=name)
|
|
||||||
except:
|
|
||||||
logger.info("Tag doesn't exists, creating tag")
|
logger.info("Tag doesn't exists, creating tag")
|
||||||
return Tag(name=name)
|
return Tag(name=name)
|
||||||
|
|
|
@ -10,6 +10,3 @@ class Tool(SqlAlchemyBase):
|
||||||
|
|
||||||
def __init__(self, tool) -> None:
|
def __init__(self, tool) -> None:
|
||||||
self.tool = tool
|
self.tool = tool
|
||||||
|
|
||||||
def str(self):
|
|
||||||
return self.tool
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from mealie.core.config import DEFAULT_GROUP
|
from mealie.core.config import settings
|
||||||
from mealie.db.models.group import Group
|
from mealie.db.models.group import Group
|
||||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, orm
|
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, orm
|
||||||
|
@ -26,12 +26,12 @@ class User(SqlAlchemyBase, BaseMixins):
|
||||||
full_name,
|
full_name,
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
group: str = DEFAULT_GROUP,
|
group: str = settings.DEFAULT_GROUP,
|
||||||
admin=False,
|
admin=False,
|
||||||
id=None,
|
id=None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
group = group if group else DEFAULT_GROUP
|
group = group or settings.DEFAULT_GROUP
|
||||||
self.full_name = full_name
|
self.full_name = full_name
|
||||||
self.email = email
|
self.email = email
|
||||||
self.group = Group.get_ref(session, group)
|
self.group = Group.get_ref(session, group)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import operator
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||||
from mealie.core.config import BACKUP_DIR, TEMPLATE_DIR
|
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.deps import get_current_user
|
||||||
from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup
|
from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup
|
||||||
|
@ -19,11 +19,11 @@ router = APIRouter(prefix="/api/backups", tags=["Backups"], dependencies=[Depend
|
||||||
def available_imports():
|
def available_imports():
|
||||||
"""Returns a list of avaiable .zip files for import into Mealie."""
|
"""Returns a list of avaiable .zip files for import into Mealie."""
|
||||||
imports = []
|
imports = []
|
||||||
for archive in BACKUP_DIR.glob("*.zip"):
|
for archive in app_dirs.app_dirs.BACKUP_DIR.glob("*.zip"):
|
||||||
backup = LocalBackup(name=archive.name, date=archive.stat().st_ctime)
|
backup = LocalBackup(name=archive.name, date=archive.stat().st_ctime)
|
||||||
imports.append(backup)
|
imports.append(backup)
|
||||||
|
|
||||||
templates = [template.name for template in TEMPLATE_DIR.glob("*.*")]
|
templates = [template.name for template in app_dirs.TEMPLATE_DIR.glob("*.*")]
|
||||||
imports.sort(key=operator.attrgetter("date"), reverse=True)
|
imports.sort(key=operator.attrgetter("date"), reverse=True)
|
||||||
|
|
||||||
return Imports(imports=imports, templates=templates)
|
return Imports(imports=imports, templates=templates)
|
||||||
|
@ -55,7 +55,7 @@ def export_database(data: BackupJob, session: Session = Depends(generate_session
|
||||||
@router.post("/upload")
|
@router.post("/upload")
|
||||||
def upload_backup_file(archive: UploadFile = File(...)):
|
def upload_backup_file(archive: UploadFile = File(...)):
|
||||||
""" Upload a .zip File to later be imported into Mealie """
|
""" Upload a .zip File to later be imported into Mealie """
|
||||||
dest = BACKUP_DIR.joinpath(archive.filename)
|
dest = app_dirs.BACKUP_DIR.joinpath(archive.filename)
|
||||||
|
|
||||||
with dest.open("wb") as buffer:
|
with dest.open("wb") as buffer:
|
||||||
shutil.copyfileobj(archive.file, buffer)
|
shutil.copyfileobj(archive.file, buffer)
|
||||||
|
@ -69,7 +69,7 @@ def upload_backup_file(archive: UploadFile = File(...)):
|
||||||
@router.get("/{file_name}/download")
|
@router.get("/{file_name}/download")
|
||||||
async def download_backup_file(file_name: str):
|
async def download_backup_file(file_name: str):
|
||||||
""" Upload a .zip File to later be imported into Mealie """
|
""" Upload a .zip File to later be imported into Mealie """
|
||||||
file = BACKUP_DIR.joinpath(file_name)
|
file = app_dirs.BACKUP_DIR.joinpath(file_name)
|
||||||
|
|
||||||
if file.is_file:
|
if file.is_file:
|
||||||
return FileResponse(file, media_type="application/octet-stream", filename=file_name)
|
return FileResponse(file, media_type="application/octet-stream", filename=file_name)
|
||||||
|
@ -100,7 +100,7 @@ def delete_backup(file_name: str):
|
||||||
""" Removes a database backup from the file system """
|
""" Removes a database backup from the file system """
|
||||||
|
|
||||||
try:
|
try:
|
||||||
BACKUP_DIR.joinpath(file_name).unlink()
|
app_dirs.BACKUP_DIR.joinpath(file_name).unlink()
|
||||||
except:
|
except:
|
||||||
HTTPException(
|
HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from mealie.core.config import APP_VERSION, DEBUG_DIR, LOGGER_FILE
|
from mealie.core.config import APP_VERSION, LOGGER_FILE, app_dirs
|
||||||
from mealie.routes.deps import get_current_user
|
from mealie.routes.deps import get_current_user
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/debug", tags=["Debug"], dependencies=[Depends(get_current_user)])
|
router = APIRouter(prefix="/api/debug", tags=["Debug"], dependencies=[Depends(get_current_user)])
|
||||||
|
@ -17,7 +17,7 @@ async def get_mealie_version():
|
||||||
async def get_last_recipe_json():
|
async def get_last_recipe_json():
|
||||||
""" Doc Str """
|
""" Doc Str """
|
||||||
|
|
||||||
with open(DEBUG_DIR.joinpath("last_recipe.json"), "r") as f:
|
with open(app_dirs.DEBUG_DIR.joinpath("last_recipe.json"), "r") as f:
|
||||||
return json.loads(f.read())
|
return json.loads(f.read())
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from fastapi import Depends, HTTPException, status
|
from fastapi import Depends, HTTPException, status
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
from jose import JWTError, jwt
|
from jose import JWTError, jwt
|
||||||
from mealie.core.config import SECRET
|
from mealie.core.config import settings
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
from mealie.db.db_setup import create_session, generate_session
|
from mealie.db.db_setup import generate_session
|
||||||
from mealie.schema.auth import Token, TokenData
|
from mealie.schema.auth import TokenData
|
||||||
from mealie.schema.user import UserInDB
|
from mealie.schema.user import UserInDB
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token")
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token")
|
||||||
|
@ -18,7 +18,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme), session=Depends(
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
payload = jwt.decode(token, SECRET, algorithms=[ALGORITHM])
|
payload = jwt.decode(token, settings.SECRET, algorithms=[ALGORITHM])
|
||||||
username: str = payload.get("sub")
|
username: str = payload.get("sub")
|
||||||
if username is None:
|
if username is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
|
@ -29,6 +29,3 @@ async def get_current_user(token: str = Depends(oauth2_scheme), session=Depends(
|
||||||
if user is None:
|
if user is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ 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.schema.snackbar import SnackResponse
|
from mealie.schema.snackbar import SnackResponse
|
||||||
from mealie.schema.user import GroupBase, GroupInDB, UpdateGroup, UserIn, UserInDB
|
from mealie.schema.user import GroupBase, GroupInDB, UpdateGroup, UserInDB
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/groups", tags=["Groups"])
|
router = APIRouter(prefix="/api/groups", tags=["Groups"])
|
||||||
|
@ -21,7 +21,7 @@ async def get_all_groups(
|
||||||
|
|
||||||
@router.get("/self", response_model=GroupInDB)
|
@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),
|
||||||
):
|
):
|
||||||
""" Returns the Group Data for the Current User """
|
""" Returns the Group Data for the Current User """
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import datetime
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -16,7 +16,7 @@ def get_shopping_list(
|
||||||
current_user=Depends(get_current_user),
|
current_user=Depends(get_current_user),
|
||||||
):
|
):
|
||||||
|
|
||||||
#! Refactor into Single Database Call
|
# ! Refactor into Single Database Call
|
||||||
mealplan = db.meals.get(session, id)
|
mealplan = db.meals.get(session, id)
|
||||||
mealplan: MealPlanInDB
|
mealplan: MealPlanInDB
|
||||||
slugs = [x.slug for x in mealplan.meals]
|
slugs = [x.slug for x in mealplan.meals]
|
||||||
|
|
|
@ -2,8 +2,8 @@ import operator
|
||||||
import shutil
|
import shutil
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
from fastapi import APIRouter, Depends, File, UploadFile
|
||||||
from mealie.core.config import MIGRATION_DIR
|
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.deps import get_current_user
|
||||||
from mealie.schema.migration import MigrationFile, Migrations
|
from mealie.schema.migration import MigrationFile, Migrations
|
||||||
|
@ -20,8 +20,8 @@ def get_avaiable_nextcloud_imports():
|
||||||
""" Returns a list of avaiable directories that can be imported into Mealie """
|
""" Returns a list of avaiable directories that can be imported into Mealie """
|
||||||
response_data = []
|
response_data = []
|
||||||
migration_dirs = [
|
migration_dirs = [
|
||||||
MIGRATION_DIR.joinpath("nextcloud"),
|
app_dirs.MIGRATION_DIR.joinpath("nextcloud"),
|
||||||
MIGRATION_DIR.joinpath("chowdown"),
|
app_dirs.MIGRATION_DIR.joinpath("chowdown"),
|
||||||
]
|
]
|
||||||
for directory in migration_dirs:
|
for directory in migration_dirs:
|
||||||
migration = Migrations(type=directory.stem)
|
migration = Migrations(type=directory.stem)
|
||||||
|
@ -39,7 +39,7 @@ def get_avaiable_nextcloud_imports():
|
||||||
@router.post("/{type}/{file_name}/import")
|
@router.post("/{type}/{file_name}/import")
|
||||||
def import_nextcloud_directory(type: str, file_name: str, session: Session = Depends(generate_session)):
|
def import_nextcloud_directory(type: str, file_name: str, session: Session = Depends(generate_session)):
|
||||||
""" Imports all the recipes in a given directory """
|
""" Imports all the recipes in a given directory """
|
||||||
file_path = MIGRATION_DIR.joinpath(type, file_name)
|
file_path = app_dirs.MIGRATION_DIR.joinpath(type, file_name)
|
||||||
if type == "nextcloud":
|
if type == "nextcloud":
|
||||||
return nextcloud_migrate(session, file_path)
|
return nextcloud_migrate(session, file_path)
|
||||||
elif type == "chowdown":
|
elif type == "chowdown":
|
||||||
|
@ -52,7 +52,7 @@ def import_nextcloud_directory(type: str, file_name: str, session: Session = Dep
|
||||||
def delete_migration_data(type: str, file_name: str):
|
def delete_migration_data(type: str, file_name: str):
|
||||||
""" Removes migration data from the file system """
|
""" Removes migration data from the file system """
|
||||||
|
|
||||||
remove_path = MIGRATION_DIR.joinpath(type, file_name)
|
remove_path = app_dirs.MIGRATION_DIR.joinpath(type, file_name)
|
||||||
|
|
||||||
if remove_path.is_file():
|
if remove_path.is_file():
|
||||||
remove_path.unlink()
|
remove_path.unlink()
|
||||||
|
@ -67,7 +67,7 @@ def delete_migration_data(type: str, file_name: str):
|
||||||
@router.post("/{type}/upload")
|
@router.post("/{type}/upload")
|
||||||
def upload_nextcloud_zipfile(type: str, archive: UploadFile = File(...)):
|
def upload_nextcloud_zipfile(type: str, archive: UploadFile = File(...)):
|
||||||
""" Upload a .zip File to later be imported into Mealie """
|
""" Upload a .zip File to later be imported into Mealie """
|
||||||
dir = MIGRATION_DIR.joinpath(type)
|
dir = app_dirs.MIGRATION_DIR.joinpath(type)
|
||||||
dir.mkdir(parents=True, exist_ok=True)
|
dir.mkdir(parents=True, exist_ok=True)
|
||||||
dest = dir.joinpath(archive.filename)
|
dest = dir.joinpath(archive.filename)
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ def get_all_recipes_post(body: AllRecipeRequest, session: Session = Depends(gene
|
||||||
@router.post("/api/recipes/category")
|
@router.post("/api/recipes/category")
|
||||||
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
|
||||||
in_category = [db.categories.get(session, slugify(cat), limit=1) for cat in categories]
|
in_category = [db.categories.get(session, slugify(cat), limit=1) for cat in categories]
|
||||||
in_category = [cat.get("recipes") for cat in in_category if cat]
|
in_category = [cat.get("recipes") for cat in in_category if cat]
|
||||||
in_category = [item for sublist in in_category for item in sublist]
|
in_category = [item for sublist in in_category for item in sublist]
|
||||||
|
@ -83,7 +83,7 @@ def filter_by_category(categories: list, session: Session = Depends(generate_ses
|
||||||
@router.post("/api/recipes/tag")
|
@router.post("/api/recipes/tag")
|
||||||
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
|
||||||
in_tags = [db.tags.get(session, slugify(tag), limit=1) for tag in tags]
|
in_tags = [db.tags.get(session, slugify(tag), limit=1) for tag in tags]
|
||||||
in_tags = [tag.get("recipes") for tag in in_tags]
|
in_tags = [tag.get("recipes") for tag in in_tags]
|
||||||
in_tags = [item for sublist in in_tags for item in sublist]
|
in_tags = [item for sublist in in_tags for item in sublist]
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
from mealie.routes.deps import get_current_user
|
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 fastapi import APIRouter, Depends
|
from mealie.routes.deps import get_current_user
|
||||||
from mealie.schema.category import RecipeCategoryResponse
|
from mealie.schema.category import RecipeCategoryResponse
|
||||||
|
from mealie.schema.snackbar import SnackResponse
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
from mealie.schema.snackbar import SnackResponse
|
|
||||||
|
|
||||||
from mealie.schema.snackbar import SnackResponse
|
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
prefix="/api/categories",
|
prefix="/api/categories",
|
||||||
|
@ -27,7 +25,9 @@ def get_all_recipes_by_category(category: str, session: Session = Depends(genera
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{category}")
|
@router.delete("/{category}")
|
||||||
async def delete_recipe_category(category: str, session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
|
async def delete_recipe_category(
|
||||||
|
category: str, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
||||||
|
):
|
||||||
"""Removes a recipe category from the database. Deleting a
|
"""Removes a recipe category from the database. Deleting a
|
||||||
category does not impact a recipe. The category will be removed
|
category does not impact a recipe. The category will be removed
|
||||||
from any recipes that contain it"""
|
from any recipes that contain it"""
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from fastapi import APIRouter, Depends, File, Form, HTTPException
|
from fastapi import APIRouter, Depends, File, Form, HTTPException
|
||||||
from fastapi.logger import logger
|
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
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
|
||||||
|
|
|
@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, status
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
from fastapi.security import OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
from mealie.core import security
|
from mealie.core import security
|
||||||
from mealie.core.security import authenticate_user, verify_password
|
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.schema.snackbar import SnackResponse
|
from mealie.schema.snackbar import SnackResponse
|
||||||
|
|
|
@ -4,7 +4,7 @@ from datetime import timedelta
|
||||||
from fastapi import APIRouter, Depends, File, UploadFile
|
from fastapi import APIRouter, Depends, File, UploadFile
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
from mealie.core import security
|
from mealie.core import security
|
||||||
from mealie.core.config import DEFAULT_PASSWORD, USER_DIR
|
from mealie.core.config import settings, app_dirs
|
||||||
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
|
||||||
|
@ -65,7 +65,7 @@ async def reset_user_password(
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
):
|
):
|
||||||
|
|
||||||
new_password = get_password_hash(DEFAULT_PASSWORD)
|
new_password = get_password_hash(settings.DEFAULT_PASSWORD)
|
||||||
db.users.update_password(session, id, new_password)
|
db.users.update_password(session, id, new_password)
|
||||||
|
|
||||||
return SnackResponse.success("Users Password Reset")
|
return SnackResponse.success("Users Password Reset")
|
||||||
|
@ -92,7 +92,7 @@ async def update_user(
|
||||||
@router.get("/{id}/image")
|
@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 = USER_DIR.joinpath(id)
|
user_dir = app_dirs.USER_DIR.joinpath(id)
|
||||||
for recipe_image in user_dir.glob("profile_image.*"):
|
for recipe_image in user_dir.glob("profile_image.*"):
|
||||||
return FileResponse(recipe_image)
|
return FileResponse(recipe_image)
|
||||||
else:
|
else:
|
||||||
|
@ -109,14 +109,14 @@ async def update_user_image(
|
||||||
|
|
||||||
extension = profile_image.filename.split(".")[-1]
|
extension = profile_image.filename.split(".")[-1]
|
||||||
|
|
||||||
USER_DIR.joinpath(id).mkdir(parents=True, exist_ok=True)
|
app_dirs.USER_DIR.joinpath(id).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
[x.unlink() for x in USER_DIR.join(id).glob("profile_image.*")]
|
[x.unlink() for x in app_dirs.USER_DIR.join(id).glob("profile_image.*")]
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
dest = USER_DIR.joinpath(id, f"profile_image.{extension}")
|
dest = app_dirs.USER_DIR.joinpath(id, f"profile_image.{extension}")
|
||||||
|
|
||||||
with dest.open("wb") as buffer:
|
with dest.open("wb") as buffer:
|
||||||
shutil.copyfileobj(profile_image.file, buffer)
|
shutil.copyfileobj(profile_image.file, buffer)
|
||||||
|
@ -160,4 +160,4 @@ async def delete_user(
|
||||||
|
|
||||||
if current_user.id == id or current_user.admin:
|
if current_user.id == id or current_user.admin:
|
||||||
db.users.delete(session, id)
|
db.users.delete(session, id)
|
||||||
return SnackResponse.error(f"User Deleted")
|
return SnackResponse.error("User Deleted")
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class Token(BaseModel):
|
class Token(BaseModel):
|
||||||
access_token: str
|
access_token: str
|
||||||
token_type: str
|
token_type: str
|
||||||
|
|
|
@ -43,6 +43,7 @@ class Recipe(BaseModel):
|
||||||
recipeIngredient: Optional[list[str]]
|
recipeIngredient: Optional[list[str]]
|
||||||
recipeInstructions: Optional[list[RecipeStep]]
|
recipeInstructions: Optional[list[RecipeStep]]
|
||||||
nutrition: Optional[Nutrition]
|
nutrition: Optional[Nutrition]
|
||||||
|
tools: Optional[list[str]] = []
|
||||||
|
|
||||||
totalTime: Optional[str] = None
|
totalTime: Optional[str] = None
|
||||||
prepTime: Optional[str] = None
|
prepTime: Optional[str] = None
|
||||||
|
@ -67,6 +68,7 @@ class Recipe(BaseModel):
|
||||||
"recipeIngredient": [x.ingredient for x in name_orm.recipeIngredient],
|
"recipeIngredient": [x.ingredient for x in name_orm.recipeIngredient],
|
||||||
"recipeCategory": [x.name for x in name_orm.recipeCategory],
|
"recipeCategory": [x.name for x in name_orm.recipeCategory],
|
||||||
"tags": [x.name for x in name_orm.tags],
|
"tags": [x.name for x in name_orm.tags],
|
||||||
|
"tools": [x.tool for x in name_orm.tools],
|
||||||
"extras": {x.key_name: x.value for x in name_orm.extras},
|
"extras": {x.key_name: x.value for x in name_orm.extras},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class WebhookJob(BaseModel):
|
|
||||||
webhook_urls: list[str] = []
|
|
||||||
webhook_time: str = "00:00"
|
|
||||||
webhook_enable: bool
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
orm_mode = True
|
|
|
@ -1,7 +1,7 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from fastapi_camelcase import CamelModel
|
from fastapi_camelcase import CamelModel
|
||||||
from mealie.core.config import DEFAULT_GROUP
|
from mealie.core.config import settings
|
||||||
from mealie.db.models.group import Group
|
from mealie.db.models.group import Group
|
||||||
from mealie.db.models.users import User
|
from mealie.db.models.users import User
|
||||||
from mealie.schema.category import CategoryBase
|
from mealie.schema.category import CategoryBase
|
||||||
|
@ -40,7 +40,7 @@ class UserBase(CamelModel):
|
||||||
schema_extra = {
|
schema_extra = {
|
||||||
"fullName": "Change Me",
|
"fullName": "Change Me",
|
||||||
"email": "changeme@email.com",
|
"email": "changeme@email.com",
|
||||||
"group": DEFAULT_GROUP,
|
"group": settings.DEFAULT_GROUP,
|
||||||
"admin": "false",
|
"admin": "false",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from typing import Union
|
||||||
|
|
||||||
from fastapi.logger import logger
|
from fastapi.logger import logger
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
from mealie.core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR, TEMPLATE_DIR
|
from mealie.core.config import app_dirs
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
from mealie.db.db_setup import create_session
|
from mealie.db.db_setup import create_session
|
||||||
from pydantic.main import BaseModel
|
from pydantic.main import BaseModel
|
||||||
|
@ -28,12 +28,12 @@ class ExportDatabase:
|
||||||
else:
|
else:
|
||||||
export_tag = datetime.now().strftime("%Y-%b-%d")
|
export_tag = datetime.now().strftime("%Y-%b-%d")
|
||||||
|
|
||||||
self.main_dir = TEMP_DIR.joinpath(export_tag)
|
self.main_dir = app_dirs.TEMP_DIR.joinpath(export_tag)
|
||||||
self.img_dir = self.main_dir.joinpath("images")
|
self.img_dir = self.main_dir.joinpath("images")
|
||||||
self.templates_dir = self.main_dir.joinpath("templates")
|
self.templates_dir = self.main_dir.joinpath("templates")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.templates = [TEMPLATE_DIR.joinpath(x) for x in templates]
|
self.templates = [app_dirs.TEMPLATE_DIR.joinpath(x) for x in templates]
|
||||||
except:
|
except:
|
||||||
self.templates = False
|
self.templates = False
|
||||||
logger.info("No Jinja2 Templates Registered for Export")
|
logger.info("No Jinja2 Templates Registered for Export")
|
||||||
|
@ -65,7 +65,7 @@ class ExportDatabase:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
def export_images(self):
|
def export_images(self):
|
||||||
for file in IMG_DIR.iterdir():
|
for file in app_dirs.IMG_DIR.iterdir():
|
||||||
shutil.copy(file, self.img_dir.joinpath(file.name))
|
shutil.copy(file, self.img_dir.joinpath(file.name))
|
||||||
|
|
||||||
def export_items(self, items: list[BaseModel], folder_name: str, export_list=True):
|
def export_items(self, items: list[BaseModel], folder_name: str, export_list=True):
|
||||||
|
@ -87,10 +87,10 @@ class ExportDatabase:
|
||||||
f.write(json_data)
|
f.write(json_data)
|
||||||
|
|
||||||
def finish_export(self):
|
def finish_export(self):
|
||||||
zip_path = BACKUP_DIR.joinpath(f"{self.main_dir.name}")
|
zip_path = app_dirs.BACKUP_DIR.joinpath(f"{self.main_dir.name}")
|
||||||
shutil.make_archive(zip_path, "zip", self.main_dir)
|
shutil.make_archive(zip_path, "zip", self.main_dir)
|
||||||
|
|
||||||
shutil.rmtree(TEMP_DIR)
|
shutil.rmtree(app_dirs.TEMP_DIR)
|
||||||
|
|
||||||
return str(zip_path.absolute()) + ".zip"
|
return str(zip_path.absolute()) + ".zip"
|
||||||
|
|
||||||
|
@ -138,10 +138,10 @@ def backup_all(
|
||||||
|
|
||||||
|
|
||||||
def auto_backup_job():
|
def auto_backup_job():
|
||||||
for backup in BACKUP_DIR.glob("Auto*.zip"):
|
for backup in app_dirs.BACKUP_DIR.glob("Auto*.zip"):
|
||||||
backup.unlink()
|
backup.unlink()
|
||||||
|
|
||||||
templates = [template for template in TEMPLATE_DIR.iterdir()]
|
templates = [template for template in app_dirs.TEMPLATE_DIR.iterdir()]
|
||||||
session = create_session()
|
session = create_session()
|
||||||
backup_all(session=session, tag="Auto", templates=templates)
|
backup_all(session=session, tag="Auto", templates=templates)
|
||||||
logger.info("Auto Backup Called")
|
logger.info("Auto Backup Called")
|
||||||
|
|
|
@ -4,7 +4,7 @@ import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, List
|
from typing import Callable, List
|
||||||
|
|
||||||
from mealie.core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR
|
from mealie.core.config import app_dirs
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
from mealie.schema.recipe import Recipe
|
from mealie.schema.recipe import Recipe
|
||||||
from mealie.schema.restore import CustomPageImport, GroupImport, RecipeImport, SettingsImport, ThemeImport, UserImport
|
from mealie.schema.restore import CustomPageImport, GroupImport, RecipeImport, SettingsImport, ThemeImport, UserImport
|
||||||
|
@ -33,11 +33,11 @@ class ImportDatabase:
|
||||||
Exception: If the zip file does not exists an exception raise.
|
Exception: If the zip file does not exists an exception raise.
|
||||||
"""
|
"""
|
||||||
self.session = session
|
self.session = session
|
||||||
self.archive = BACKUP_DIR.joinpath(zip_archive)
|
self.archive = app_dirs.BACKUP_DIR.joinpath(zip_archive)
|
||||||
self.force_imports = force_import
|
self.force_imports = force_import
|
||||||
|
|
||||||
if self.archive.is_file():
|
if self.archive.is_file():
|
||||||
self.import_dir = TEMP_DIR.joinpath("active_import")
|
self.import_dir = app_dirs.TEMP_DIR.joinpath("active_import")
|
||||||
self.import_dir.mkdir(parents=True, exist_ok=True)
|
self.import_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
with zipfile.ZipFile(self.archive, "r") as zip_ref:
|
with zipfile.ZipFile(self.archive, "r") as zip_ref:
|
||||||
|
@ -108,7 +108,7 @@ class ImportDatabase:
|
||||||
image_dir = self.import_dir.joinpath("images")
|
image_dir = self.import_dir.joinpath("images")
|
||||||
for image in image_dir.iterdir():
|
for image in image_dir.iterdir():
|
||||||
if image.stem in successful_imports:
|
if image.stem in successful_imports:
|
||||||
shutil.copy(image, IMG_DIR)
|
shutil.copy(image, app_dirs.IMG_DIR)
|
||||||
|
|
||||||
def import_themes(self):
|
def import_themes(self):
|
||||||
themes_file = self.import_dir.joinpath("themes", "themes.json")
|
themes_file = self.import_dir.joinpath("themes", "themes.json")
|
||||||
|
@ -131,7 +131,7 @@ class ImportDatabase:
|
||||||
|
|
||||||
return theme_imports
|
return theme_imports
|
||||||
|
|
||||||
def import_settings(self): #! Broken
|
def import_settings(self): # ! Broken
|
||||||
settings_file = self.import_dir.joinpath("settings", "settings.json")
|
settings_file = self.import_dir.joinpath("settings", "settings.json")
|
||||||
settings = ImportDatabase.read_models_file(settings_file, SiteSettings)
|
settings = ImportDatabase.read_models_file(settings_file, SiteSettings)
|
||||||
settings = settings[0]
|
settings = settings[0]
|
||||||
|
@ -275,7 +275,7 @@ class ImportDatabase:
|
||||||
return import_status
|
return import_status
|
||||||
|
|
||||||
def clean_up(self):
|
def clean_up(self):
|
||||||
shutil.rmtree(TEMP_DIR)
|
shutil.rmtree(app_dirs.TEMP_DIR)
|
||||||
|
|
||||||
|
|
||||||
def import_database(
|
def import_database(
|
||||||
|
|
|
@ -2,23 +2,23 @@ import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from mealie.core.config import IMG_DIR
|
|
||||||
from fastapi.logger import logger
|
from fastapi.logger import logger
|
||||||
|
from mealie.core.config import app_dirs
|
||||||
|
|
||||||
|
|
||||||
def read_image(recipe_slug: str) -> Path:
|
def read_image(recipe_slug: str) -> Path:
|
||||||
if IMG_DIR.joinpath(recipe_slug).is_file():
|
if app_dirs.IMG_DIR.joinpath(recipe_slug).is_file():
|
||||||
return IMG_DIR.joinpath(recipe_slug)
|
return app_dirs.IMG_DIR.joinpath(recipe_slug)
|
||||||
else:
|
|
||||||
recipe_slug = recipe_slug.split(".")[0]
|
recipe_slug = recipe_slug.split(".")[0]
|
||||||
for file in IMG_DIR.glob(f"{recipe_slug}*"):
|
for file in app_dirs.IMG_DIR.glob(f"{recipe_slug}*"):
|
||||||
return file
|
return file
|
||||||
|
|
||||||
|
|
||||||
def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path.name:
|
def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path.name:
|
||||||
delete_image(recipe_slug)
|
delete_image(recipe_slug)
|
||||||
|
|
||||||
image_path = Path(IMG_DIR.joinpath(f"{recipe_slug}.{extension}"))
|
image_path = Path(app_dirs.IMG_DIR.joinpath(f"{recipe_slug}.{extension}"))
|
||||||
with open(image_path, "ab") as f:
|
with open(image_path, "ab") as f:
|
||||||
f.write(file_data)
|
f.write(file_data)
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path.name
|
||||||
|
|
||||||
def delete_image(recipe_slug: str) -> str:
|
def delete_image(recipe_slug: str) -> str:
|
||||||
recipe_slug = recipe_slug.split(".")[0]
|
recipe_slug = recipe_slug.split(".")[0]
|
||||||
for file in IMG_DIR.glob(f"{recipe_slug}*"):
|
for file in app_dirs.IMG_DIR.glob(f"{recipe_slug}*"):
|
||||||
return file.unlink()
|
return file.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ def scrape_image(image_url: str, slug: str) -> Path:
|
||||||
image_url = image_url.get("url")
|
image_url = image_url.get("url")
|
||||||
|
|
||||||
filename = slug + "." + image_url.split(".")[-1]
|
filename = slug + "." + image_url.split(".")[-1]
|
||||||
filename = IMG_DIR.joinpath(filename)
|
filename = app_dirs.IMG_DIR.joinpath(filename)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = requests.get(image_url, stream=True)
|
r = requests.get(image_url, stream=True)
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
from datetime import date, timedelta, timezone
|
from datetime import date, timedelta
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import pytz
|
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
from mealie.db.db_setup import create_session
|
from mealie.db.db_setup import create_session
|
||||||
from mealie.schema.meal import MealIn, MealOut, MealPlanIn, MealPlanInDB, MealPlanProcessed
|
from mealie.schema.meal import MealIn, MealOut, MealPlanIn, MealPlanInDB, MealPlanProcessed
|
||||||
from mealie.schema.recipe import Recipe
|
from mealie.schema.recipe import Recipe
|
||||||
from mealie.schema.user import GroupInDB
|
from mealie.schema.user import GroupInDB
|
||||||
from pydantic.tools import T
|
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from fastapi.logger import logger
|
from fastapi.logger import logger
|
||||||
from mealie.core.config import IMG_DIR, TEMP_DIR
|
from mealie.core.config import app_dirs
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
from mealie.schema.recipe import Recipe
|
from mealie.schema.recipe import Recipe
|
||||||
from mealie.utils.unzip import unpack_zip
|
from mealie.utils.unzip import unpack_zip
|
||||||
|
@ -64,8 +64,8 @@ def chowdown_migrate(session: Session, zip_file: Path):
|
||||||
|
|
||||||
with temp_dir as dir:
|
with temp_dir as dir:
|
||||||
chow_dir = next(Path(dir).iterdir())
|
chow_dir = next(Path(dir).iterdir())
|
||||||
image_dir = TEMP_DIR.joinpath(chow_dir, "images")
|
image_dir = app_dirs.TEMP_DIR.joinpath(chow_dir, "images")
|
||||||
recipe_dir = TEMP_DIR.joinpath(chow_dir, "_recipes")
|
recipe_dir = app_dirs.TEMP_DIR.joinpath(chow_dir, "_recipes")
|
||||||
|
|
||||||
failed_recipes = []
|
failed_recipes = []
|
||||||
successful_recipes = []
|
successful_recipes = []
|
||||||
|
@ -83,7 +83,7 @@ def chowdown_migrate(session: Session, zip_file: Path):
|
||||||
for image in image_dir.iterdir():
|
for image in image_dir.iterdir():
|
||||||
try:
|
try:
|
||||||
if image.stem not in failed_recipes:
|
if image.stem not in failed_recipes:
|
||||||
shutil.copy(image, IMG_DIR.joinpath(image.name))
|
shutil.copy(image, app_dirs.IMG_DIR.joinpath(image.name))
|
||||||
except Exception as inst:
|
except Exception as inst:
|
||||||
logger.error(inst)
|
logger.error(inst)
|
||||||
failed_images.append(image.name)
|
failed_images.append(image.name)
|
||||||
|
|
|
@ -4,11 +4,10 @@ import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from mealie.core.config import IMG_DIR, MIGRATION_DIR, TEMP_DIR
|
from mealie.core.config import app_dirs
|
||||||
|
from mealie.db.database import db
|
||||||
from mealie.schema.recipe import Recipe
|
from mealie.schema.recipe import Recipe
|
||||||
from mealie.services.scraper.cleaner import Cleaner
|
from mealie.services.scraper.cleaner import Cleaner
|
||||||
from mealie.core.config import IMG_DIR, TEMP_DIR
|
|
||||||
from mealie.db.database import db
|
|
||||||
|
|
||||||
|
|
||||||
def process_selection(selection: Path) -> Path:
|
def process_selection(selection: Path) -> Path:
|
||||||
|
@ -16,7 +15,7 @@ def process_selection(selection: Path) -> Path:
|
||||||
return selection
|
return selection
|
||||||
elif selection.suffix == ".zip":
|
elif selection.suffix == ".zip":
|
||||||
with zipfile.ZipFile(selection, "r") as zip_ref:
|
with zipfile.ZipFile(selection, "r") as zip_ref:
|
||||||
nextcloud_dir = TEMP_DIR.joinpath("nextcloud")
|
nextcloud_dir = app_dirs.TEMP_DIR.joinpath("nextcloud")
|
||||||
nextcloud_dir.mkdir(exist_ok=False, parents=True)
|
nextcloud_dir.mkdir(exist_ok=False, parents=True)
|
||||||
zip_ref.extractall(nextcloud_dir)
|
zip_ref.extractall(nextcloud_dir)
|
||||||
return nextcloud_dir
|
return nextcloud_dir
|
||||||
|
@ -47,27 +46,27 @@ def import_recipes(recipe_dir: Path) -> Recipe:
|
||||||
recipe = Recipe(**recipe_data)
|
recipe = Recipe(**recipe_data)
|
||||||
|
|
||||||
if image:
|
if image:
|
||||||
shutil.copy(image, IMG_DIR.joinpath(image_name))
|
shutil.copy(image, app_dirs.IMG_DIR.joinpath(image_name))
|
||||||
|
|
||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
|
|
||||||
def prep():
|
def prep():
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(TEMP_DIR)
|
shutil.rmtree(app_dirs.TEMP_DIR)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
TEMP_DIR.mkdir(exist_ok=True, parents=True)
|
app_dirs.TEMP_DIR.mkdir(exist_ok=True, parents=True)
|
||||||
|
|
||||||
|
|
||||||
def cleanup():
|
def cleanup():
|
||||||
shutil.rmtree(TEMP_DIR)
|
shutil.rmtree(app_dirs.TEMP_DIR)
|
||||||
|
|
||||||
|
|
||||||
def migrate(session, selection: str):
|
def migrate(session, selection: str):
|
||||||
prep()
|
prep()
|
||||||
MIGRATION_DIR.mkdir(exist_ok=True)
|
app_dirs.MIGRATION_DIR.mkdir(exist_ok=True)
|
||||||
selection = MIGRATION_DIR.joinpath(selection)
|
selection = app_dirs.MIGRATION_DIR.joinpath(selection)
|
||||||
|
|
||||||
nextcloud_dir = process_selection(selection)
|
nextcloud_dir = process_selection(selection)
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ class Cleaner:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def category(category: str):
|
def category(category: str):
|
||||||
if type(category) == type(str):
|
if isinstance(category, str):
|
||||||
return [category]
|
return [category]
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
@ -58,11 +58,11 @@ class Cleaner:
|
||||||
def image(image=None) -> str:
|
def image(image=None) -> str:
|
||||||
if not image:
|
if not image:
|
||||||
return "no image"
|
return "no image"
|
||||||
if type(image) == list:
|
if isinstance(image, list):
|
||||||
return image[0]
|
return image[0]
|
||||||
elif type(image) == dict:
|
elif isinstance(image, dict):
|
||||||
return image["url"]
|
return image["url"]
|
||||||
elif type(image) == str:
|
elif isinstance(image, str):
|
||||||
return image
|
return image
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Unrecognised image URL format: {image}")
|
raise Exception(f"Unrecognised image URL format: {image}")
|
||||||
|
@ -77,11 +77,11 @@ class Cleaner:
|
||||||
return [{"text": Cleaner._instruction(line)} for line in instructions.splitlines() if line]
|
return [{"text": Cleaner._instruction(line)} for line in instructions.splitlines() if line]
|
||||||
|
|
||||||
# Plain strings in a list
|
# Plain strings in a list
|
||||||
elif type(instructions) == list and type(instructions[0]) == str:
|
elif isinstance(instructions, list) and isinstance(instructions[0], str):
|
||||||
return [{"text": Cleaner._instruction(step)} for step in instructions]
|
return [{"text": Cleaner._instruction(step)} for step in instructions]
|
||||||
|
|
||||||
# Dictionaries (let's assume it's a HowToStep) in a list
|
# Dictionaries (let's assume it's a HowToStep) in a list
|
||||||
elif type(instructions) == list and type(instructions[0]) == dict:
|
elif isinstance(instructions, list) and isinstance(instructions[0], dict):
|
||||||
# Try List of Dictionary without "@type" or "type"
|
# Try List of Dictionary without "@type" or "type"
|
||||||
if not instructions[0].get("@type", False) and not instructions[0].get("type", False):
|
if not instructions[0].get("@type", False) and not instructions[0].get("type", False):
|
||||||
return [{"text": Cleaner._instruction(step["text"])} for step in instructions]
|
return [{"text": Cleaner._instruction(step["text"])} for step in instructions]
|
||||||
|
@ -106,6 +106,7 @@ class Cleaner:
|
||||||
if step["@type"] == "HowToStep"
|
if step["@type"] == "HowToStep"
|
||||||
]
|
]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
# Not "@type", try "type"
|
# Not "@type", try "type"
|
||||||
try:
|
try:
|
||||||
return [
|
return [
|
||||||
|
@ -121,11 +122,11 @@ class Cleaner:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _instruction(line) -> str:
|
def _instruction(line) -> str:
|
||||||
l = Cleaner.html(line.strip())
|
clean_line = Cleaner.html(line.strip())
|
||||||
# Some sites erroneously escape their strings on multiple levels
|
# Some sites erroneously escape their strings on multiple levels
|
||||||
while not l == (l := html.unescape(l)):
|
while not clean_line == (clean_line := html.unescape(clean_line)):
|
||||||
pass
|
pass
|
||||||
return l
|
return clean_line
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def ingredient(ingredients: list) -> str:
|
def ingredient(ingredients: list) -> str:
|
||||||
|
@ -134,7 +135,7 @@ class Cleaner:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def yield_amount(yld) -> str:
|
def yield_amount(yld) -> str:
|
||||||
if type(yld) == list:
|
if isinstance(yld, list):
|
||||||
return yld[-1]
|
return yld[-1]
|
||||||
else:
|
else:
|
||||||
return yld
|
return yld
|
||||||
|
@ -143,9 +144,9 @@ class Cleaner:
|
||||||
def time(time_entry):
|
def time(time_entry):
|
||||||
if time_entry is None:
|
if time_entry is None:
|
||||||
return None
|
return None
|
||||||
elif type(time_entry) == datetime:
|
elif isinstance(time_entry, datetime):
|
||||||
print(time_entry)
|
print(time_entry)
|
||||||
elif type(time_entry) != str:
|
elif isinstance(time_entry, str):
|
||||||
return str(time_entry)
|
return str(time_entry)
|
||||||
else:
|
else:
|
||||||
return time_entry
|
return time_entry
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
import extruct
|
import extruct
|
||||||
from mealie.core.config import DEBUG_DIR
|
from mealie.core.config import app_dirs
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
from w3lib.html import get_base_url
|
from w3lib.html import get_base_url
|
||||||
|
|
||||||
LAST_JSON = DEBUG_DIR.joinpath("last_recipe.json")
|
LAST_JSON = app_dirs.DEBUG_DIR.joinpath("last_recipe.json")
|
||||||
|
|
||||||
|
|
||||||
def og_field(properties: dict, field_name: str) -> str:
|
def og_field(properties: dict, field_name: str) -> str:
|
||||||
|
|
|
@ -3,14 +3,14 @@ from typing import List
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import scrape_schema_recipe
|
import scrape_schema_recipe
|
||||||
from mealie.core.config import DEBUG_DIR
|
from mealie.core.config import app_dirs
|
||||||
from fastapi.logger import logger
|
from fastapi.logger import logger
|
||||||
from mealie.services.image_services import scrape_image
|
from mealie.services.image_services import scrape_image
|
||||||
from mealie.schema.recipe import Recipe
|
from mealie.schema.recipe import Recipe
|
||||||
from mealie.services.scraper import open_graph
|
from mealie.services.scraper import open_graph
|
||||||
from mealie.services.scraper.cleaner import Cleaner
|
from mealie.services.scraper.cleaner import Cleaner
|
||||||
|
|
||||||
LAST_JSON = DEBUG_DIR.joinpath("last_recipe.json")
|
LAST_JSON = app_dirs.DEBUG_DIR.joinpath("last_recipe.json")
|
||||||
|
|
||||||
|
|
||||||
def create_from_url(url: str) -> Recipe:
|
def create_from_url(url: str) -> Recipe:
|
||||||
|
@ -39,7 +39,7 @@ def extract_recipe_from_html(html: str, url: str) -> dict:
|
||||||
if not scraped_recipes:
|
if not scraped_recipes:
|
||||||
scraped_recipes: List[dict] = scrape_schema_recipe.scrape_url(url, python_objects=True)
|
scraped_recipes: List[dict] = scrape_schema_recipe.scrape_url(url, python_objects=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# trying without python_objects
|
print(e)
|
||||||
scraped_recipes: List[dict] = scrape_schema_recipe.loads(html)
|
scraped_recipes: List[dict] = scrape_schema_recipe.loads(html)
|
||||||
dump_last_json(scraped_recipes)
|
dump_last_json(scraped_recipes)
|
||||||
|
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
import json
|
|
||||||
|
|
||||||
from mealie.core.config import DATA_DIR
|
|
||||||
|
|
||||||
"""Script to export the ReDoc documentation page into a standalone HTML file."""
|
|
||||||
|
|
||||||
HTML_TEMPLATE = """<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
|
||||||
<title>My Project - ReDoc</title>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<style data-styled="" data-styled-version="4.4.1"></style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="redoc-container"></div>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js"> </script>
|
|
||||||
<script>
|
|
||||||
var spec = %s;
|
|
||||||
Redoc.init(spec, {}, document.getElementById("redoc-container"));
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
HTML_PATH = DATA_DIR.parent.joinpath("docs/docs/html/api.html")
|
|
||||||
|
|
||||||
|
|
||||||
def generate_api_docs(app):
|
|
||||||
with open(HTML_PATH, "w") as fd:
|
|
||||||
print(HTML_TEMPLATE % json.dumps(app.openapi()), file=fd)
|
|
|
@ -7,7 +7,7 @@ from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
|
|
||||||
def post_webhooks(group: int, session: Session = None):
|
def post_webhooks(group: int, session: Session = None):
|
||||||
session = session if session else create_session()
|
session = session or create_session()
|
||||||
group_settings: GroupInDB = db.groups.get(session, group)
|
group_settings: GroupInDB = db.groups.get(session, group)
|
||||||
|
|
||||||
if not group_settings.webhook_enable:
|
if not group_settings.webhook_enable:
|
||||||
|
|
|
@ -2,12 +2,12 @@ import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from mealie.core.config import TEMP_DIR
|
from mealie.core.config import app_dirs
|
||||||
|
|
||||||
|
|
||||||
def unpack_zip(selection: Path) -> tempfile.TemporaryDirectory:
|
def unpack_zip(selection: Path) -> tempfile.TemporaryDirectory:
|
||||||
TEMP_DIR.mkdir(parents=True, exist_ok=True)
|
app_dirs.TEMP_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
temp_dir = tempfile.TemporaryDirectory(dir=TEMP_DIR)
|
temp_dir = tempfile.TemporaryDirectory(dir=app_dirs.TEMP_DIR)
|
||||||
temp_dir_path = Path(temp_dir.name)
|
temp_dir_path = Path(temp_dir.name)
|
||||||
if selection.suffix == ".zip":
|
if selection.suffix == ".zip":
|
||||||
with zipfile.ZipFile(selection, "r") as zip_ref:
|
with zipfile.ZipFile(selection, "r") as zip_ref:
|
||||||
|
|
113
poetry.lock
generated
113
poetry.lock
generated
|
@ -260,6 +260,19 @@ python-versions = ">=3.6"
|
||||||
pydantic = "*"
|
pydantic = "*"
|
||||||
pyhumps = "*"
|
pyhumps = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flake8"
|
||||||
|
version = "3.9.0"
|
||||||
|
description = "the modular source code checker: pep8 pyflakes and co"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
mccabe = ">=0.6.0,<0.7.0"
|
||||||
|
pycodestyle = ">=2.7.0,<2.8.0"
|
||||||
|
pyflakes = ">=2.3.0,<2.4.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "future"
|
name = "future"
|
||||||
version = "0.18.2"
|
version = "0.18.2"
|
||||||
|
@ -620,6 +633,14 @@ category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycodestyle"
|
||||||
|
version = "2.7.0"
|
||||||
|
description = "Python style guide checker"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pycparser"
|
name = "pycparser"
|
||||||
version = "2.20"
|
version = "2.20"
|
||||||
|
@ -643,6 +664,14 @@ typing-extensions = ">=3.7.4.3"
|
||||||
dotenv = ["python-dotenv (>=0.10.4)"]
|
dotenv = ["python-dotenv (>=0.10.4)"]
|
||||||
email = ["email-validator (>=1.0.3)"]
|
email = ["email-validator (>=1.0.3)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyflakes"
|
||||||
|
version = "2.3.1"
|
||||||
|
description = "passive checker of Python programs"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.8.1"
|
version = "2.8.1"
|
||||||
|
@ -1125,7 +1154,7 @@ python-versions = "*"
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "688326ef0f3bf3b2d2d515b941dbca379f26b08ae83afab66fa0ec95dc2c57ce"
|
content-hash = "a6c10e179bc15efc30627c9793218bb944f43dce5e624a7bcabcc47545e661e8"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
aiofiles = [
|
aiofiles = [
|
||||||
|
@ -1301,6 +1330,10 @@ fastapi = [
|
||||||
fastapi-camelcase = [
|
fastapi-camelcase = [
|
||||||
{file = "fastapi_camelcase-1.0.2.tar.gz", hash = "sha256:1d852149f6c9e5bb8002839a1e024050af917f1944b9d108d56468d64c6da279"},
|
{file = "fastapi_camelcase-1.0.2.tar.gz", hash = "sha256:1d852149f6c9e5bb8002839a1e024050af917f1944b9d108d56468d64c6da279"},
|
||||||
]
|
]
|
||||||
|
flake8 = [
|
||||||
|
{file = "flake8-3.9.0-py2.py3-none-any.whl", hash = "sha256:12d05ab02614b6aee8df7c36b97d1a3b2372761222b19b58621355e82acddcff"},
|
||||||
|
{file = "flake8-3.9.0.tar.gz", hash = "sha256:78873e372b12b093da7b5e5ed302e8ad9e988b38b063b61ad937f26ca58fc5f0"},
|
||||||
|
]
|
||||||
future = [
|
future = [
|
||||||
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
|
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
|
||||||
]
|
]
|
||||||
|
@ -1436,39 +1469,43 @@ lunr = [
|
||||||
{file = "lunr-0.5.8.tar.gz", hash = "sha256:c4fb063b98eff775dd638b3df380008ae85e6cb1d1a24d1cd81a10ef6391c26e"},
|
{file = "lunr-0.5.8.tar.gz", hash = "sha256:c4fb063b98eff775dd638b3df380008ae85e6cb1d1a24d1cd81a10ef6391c26e"},
|
||||||
]
|
]
|
||||||
lxml = [
|
lxml = [
|
||||||
{file = "lxml-4.6.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2"},
|
{file = "lxml-4.6.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a9d6bc8642e2c67db33f1247a77c53476f3a166e09067c0474facb045756087f"},
|
||||||
{file = "lxml-4.6.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f"},
|
{file = "lxml-4.6.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:791394449e98243839fa822a637177dd42a95f4883ad3dec2a0ce6ac99fb0a9d"},
|
||||||
{file = "lxml-4.6.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:079f3ae844f38982d156efce585bc540c16a926d4436712cf4baee0cce487a3d"},
|
{file = "lxml-4.6.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:68a5d77e440df94011214b7db907ec8f19e439507a70c958f750c18d88f995d2"},
|
||||||
{file = "lxml-4.6.2-cp27-cp27m-win32.whl", hash = "sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106"},
|
{file = "lxml-4.6.2-cp27-cp27m-win32.whl", hash = "sha256:fc37870d6716b137e80d19241d0e2cff7a7643b925dfa49b4c8ebd1295eb506e"},
|
||||||
{file = "lxml-4.6.2-cp27-cp27m-win_amd64.whl", hash = "sha256:8157dadbb09a34a6bd95a50690595e1fa0af1a99445e2744110e3dca7831c4ee"},
|
{file = "lxml-4.6.2-cp27-cp27m-win_amd64.whl", hash = "sha256:69a63f83e88138ab7642d8f61418cf3180a4d8cd13995df87725cb8b893e950e"},
|
||||||
{file = "lxml-4.6.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7728e05c35412ba36d3e9795ae8995e3c86958179c9770e65558ec3fdfd3724f"},
|
{file = "lxml-4.6.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:42ebca24ba2a21065fb546f3e6bd0c58c3fe9ac298f3a320147029a4850f51a2"},
|
||||||
{file = "lxml-4.6.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4"},
|
{file = "lxml-4.6.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f83d281bb2a6217cd806f4cf0ddded436790e66f393e124dfe9731f6b3fb9afe"},
|
||||||
{file = "lxml-4.6.2-cp35-cp35m-win32.whl", hash = "sha256:f2380a6376dfa090227b663f9678150ef27543483055cc327555fb592c5967e2"},
|
{file = "lxml-4.6.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:535f067002b0fd1a4e5296a8f1bf88193080ff992a195e66964ef2a6cfec5388"},
|
||||||
{file = "lxml-4.6.2-cp35-cp35m-win_amd64.whl", hash = "sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4"},
|
{file = "lxml-4.6.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:366cb750140f221523fa062d641393092813b81e15d0e25d9f7c6025f910ee80"},
|
||||||
{file = "lxml-4.6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d2e35d7bf1c1ac8c538f88d26b396e73dd81440d59c1ef8522e1ea77b345ede4"},
|
{file = "lxml-4.6.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:97db258793d193c7b62d4e2586c6ed98d51086e93f9a3af2b2034af01450a74b"},
|
||||||
{file = "lxml-4.6.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:289e9ca1a9287f08daaf796d96e06cb2bc2958891d7911ac7cae1c5f9e1e0ee3"},
|
{file = "lxml-4.6.2-cp35-cp35m-win32.whl", hash = "sha256:648914abafe67f11be7d93c1a546068f8eff3c5fa938e1f94509e4a5d682b2d8"},
|
||||||
{file = "lxml-4.6.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d"},
|
{file = "lxml-4.6.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4e751e77006da34643ab782e4a5cc21ea7b755551db202bc4d3a423b307db780"},
|
||||||
{file = "lxml-4.6.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:820628b7b3135403540202e60551e741f9b6d3304371712521be939470b454ec"},
|
{file = "lxml-4.6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:681d75e1a38a69f1e64ab82fe4b1ed3fd758717bed735fb9aeaa124143f051af"},
|
||||||
{file = "lxml-4.6.2-cp36-cp36m-win32.whl", hash = "sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04"},
|
{file = "lxml-4.6.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:127f76864468d6630e1b453d3ffbbd04b024c674f55cf0a30dc2595137892d37"},
|
||||||
{file = "lxml-4.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:92e821e43ad382332eade6812e298dc9701c75fe289f2a2d39c7960b43d1e92a"},
|
{file = "lxml-4.6.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4fb85c447e288df535b17ebdebf0ec1cf3a3f1a8eba7e79169f4f37af43c6b98"},
|
||||||
{file = "lxml-4.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:efd7a09678fd8b53117f6bae4fa3825e0a22b03ef0a932e070c0bdbb3a35e654"},
|
{file = "lxml-4.6.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5be4a2e212bb6aa045e37f7d48e3e1e4b6fd259882ed5a00786f82e8c37ce77d"},
|
||||||
{file = "lxml-4.6.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:efac139c3f0bf4f0939f9375af4b02c5ad83a622de52d6dfa8e438e8e01d0eb0"},
|
{file = "lxml-4.6.2-cp36-cp36m-win32.whl", hash = "sha256:8c88b599e226994ad4db29d93bc149aa1aff3dc3a4355dd5757569ba78632bdf"},
|
||||||
{file = "lxml-4.6.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0fbcf5565ac01dff87cbfc0ff323515c823081c5777a9fc7703ff58388c258c3"},
|
{file = "lxml-4.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:6e4183800f16f3679076dfa8abf2db3083919d7e30764a069fb66b2b9eff9939"},
|
||||||
{file = "lxml-4.6.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:122fba10466c7bd4178b07dba427aa516286b846b2cbd6f6169141917283aae2"},
|
{file = "lxml-4.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d8d3d4713f0c28bdc6c806a278d998546e8efc3498949e3ace6e117462ac0a5e"},
|
||||||
{file = "lxml-4.6.2-cp37-cp37m-win32.whl", hash = "sha256:3439c71103ef0e904ea0a1901611863e51f50b5cd5e8654a151740fde5e1cade"},
|
{file = "lxml-4.6.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:8246f30ca34dc712ab07e51dc34fea883c00b7ccb0e614651e49da2c49a30711"},
|
||||||
{file = "lxml-4.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4289728b5e2000a4ad4ab8da6e1db2e093c63c08bdc0414799ee776a3f78da4b"},
|
{file = "lxml-4.6.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:923963e989ffbceaa210ac37afc9b906acebe945d2723e9679b643513837b089"},
|
||||||
{file = "lxml-4.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa"},
|
{file = "lxml-4.6.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:1471cee35eba321827d7d53d104e7b8c593ea3ad376aa2df89533ce8e1b24a01"},
|
||||||
{file = "lxml-4.6.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:76fa7b1362d19f8fbd3e75fe2fb7c79359b0af8747e6f7141c338f0bee2f871a"},
|
{file = "lxml-4.6.2-cp37-cp37m-win32.whl", hash = "sha256:2363c35637d2d9d6f26f60a208819e7eafc4305ce39dc1d5005eccc4593331c2"},
|
||||||
{file = "lxml-4.6.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:26e761ab5b07adf5f555ee82fb4bfc35bf93750499c6c7614bd64d12aaa67927"},
|
{file = "lxml-4.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f4822c0660c3754f1a41a655e37cb4dbbc9be3d35b125a37fab6f82d47674ebc"},
|
||||||
{file = "lxml-4.6.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791"},
|
{file = "lxml-4.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0448576c148c129594d890265b1a83b9cd76fd1f0a6a04620753d9a6bcfd0a4d"},
|
||||||
{file = "lxml-4.6.2-cp38-cp38-win32.whl", hash = "sha256:89b8b22a5ff72d89d48d0e62abb14340d9e99fd637d046c27b8b257a01ffbe28"},
|
{file = "lxml-4.6.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:60a20bfc3bd234d54d49c388950195d23a5583d4108e1a1d47c9eef8d8c042b3"},
|
||||||
{file = "lxml-4.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:2a9d50e69aac3ebee695424f7dbd7b8c6d6eb7de2a2eb6b0f6c7db6aa41e02b7"},
|
{file = "lxml-4.6.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2e5cc908fe43fe1aa299e58046ad66981131a66aea3129aac7770c37f590a644"},
|
||||||
{file = "lxml-4.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ce256aaa50f6cc9a649c51be3cd4ff142d67295bfc4f490c9134d0f9f6d58ef0"},
|
{file = "lxml-4.6.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:50c348995b47b5a4e330362cf39fc503b4a43b14a91c34c83b955e1805c8e308"},
|
||||||
{file = "lxml-4.6.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7610b8c31688f0b1be0ef882889817939490a36d0ee880ea562a4e1399c447a1"},
|
{file = "lxml-4.6.2-cp38-cp38-win32.whl", hash = "sha256:94d55bd03d8671686e3f012577d9caa5421a07286dd351dfef64791cf7c6c505"},
|
||||||
{file = "lxml-4.6.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f8380c03e45cf09f8557bdaa41e1fa7c81f3ae22828e1db470ab2a6c96d8bc23"},
|
{file = "lxml-4.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:7a7669ff50f41225ca5d6ee0a1ec8413f3a0d8aa2b109f86d540887b7ec0d72a"},
|
||||||
{file = "lxml-4.6.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:884ab9b29feaca361f7f88d811b1eea9bfca36cf3da27768d28ad45c3ee6f969"},
|
{file = "lxml-4.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e0bfe9bb028974a481410432dbe1b182e8191d5d40382e5b8ff39cdd2e5c5931"},
|
||||||
{file = "lxml-4.6.2-cp39-cp39-win32.whl", hash = "sha256:33bb934a044cf32157c12bfcfbb6649807da20aa92c062ef51903415c704704f"},
|
{file = "lxml-4.6.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6fd8d5903c2e53f49e99359b063df27fdf7acb89a52b6a12494208bf61345a03"},
|
||||||
{file = "lxml-4.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83"},
|
{file = "lxml-4.6.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7e9eac1e526386df7c70ef253b792a0a12dd86d833b1d329e038c7a235dfceb5"},
|
||||||
|
{file = "lxml-4.6.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7ee8af0b9f7de635c61cdd5b8534b76c52cd03536f29f51151b377f76e214a1a"},
|
||||||
|
{file = "lxml-4.6.2-cp39-cp39-win32.whl", hash = "sha256:2e6fd1b8acd005bd71e6c94f30c055594bbd0aa02ef51a22bbfa961ab63b2d75"},
|
||||||
|
{file = "lxml-4.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:535332fe9d00c3cd455bd3dd7d4bacab86e2d564bdf7606079160fa6251caacf"},
|
||||||
|
{file = "lxml-4.6.2.tar.gz", hash = "sha256:cd11c7e8d21af997ee8079037fff88f16fda188a9776eb4b81c7e4c9c0a7d7fc"},
|
||||||
]
|
]
|
||||||
markdown = [
|
markdown = [
|
||||||
{file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"},
|
{file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"},
|
||||||
|
@ -1589,6 +1626,10 @@ pyasn1 = [
|
||||||
{file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"},
|
{file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"},
|
||||||
{file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
|
{file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
|
||||||
]
|
]
|
||||||
|
pycodestyle = [
|
||||||
|
{file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
|
||||||
|
{file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
|
||||||
|
]
|
||||||
pycparser = [
|
pycparser = [
|
||||||
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
|
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
|
||||||
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
|
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
|
||||||
|
@ -1617,6 +1658,10 @@ pydantic = [
|
||||||
{file = "pydantic-1.8.1-py3-none-any.whl", hash = "sha256:e3f8790c47ac42549dc8b045a67b0ca371c7f66e73040d0197ce6172b385e520"},
|
{file = "pydantic-1.8.1-py3-none-any.whl", hash = "sha256:e3f8790c47ac42549dc8b045a67b0ca371c7f66e73040d0197ce6172b385e520"},
|
||||||
{file = "pydantic-1.8.1.tar.gz", hash = "sha256:26cf3cb2e68ec6c0cfcb6293e69fb3450c5fd1ace87f46b64f678b0d29eac4c3"},
|
{file = "pydantic-1.8.1.tar.gz", hash = "sha256:26cf3cb2e68ec6c0cfcb6293e69fb3450c5fd1ace87f46b64f678b0d29eac4c3"},
|
||||||
]
|
]
|
||||||
|
pyflakes = [
|
||||||
|
{file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
|
||||||
|
{file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
|
||||||
|
]
|
||||||
pygments = [
|
pygments = [
|
||||||
{file = "Pygments-2.8.1-py3-none-any.whl", hash = "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"},
|
{file = "Pygments-2.8.1-py3-none-any.whl", hash = "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"},
|
||||||
{file = "Pygments-2.8.1.tar.gz", hash = "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94"},
|
{file = "Pygments-2.8.1.tar.gz", hash = "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94"},
|
||||||
|
|
|
@ -37,6 +37,8 @@ black = "^20.8b1"
|
||||||
pytest = "^6.2.1"
|
pytest = "^6.2.1"
|
||||||
pytest-cov = "^2.11.0"
|
pytest-cov = "^2.11.0"
|
||||||
mkdocs-material = "^7.0.2"
|
mkdocs-material = "^7.0.2"
|
||||||
|
flake8 = "^3.9.0"
|
||||||
|
coverage = "^5.5"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
@ -54,3 +56,6 @@ python_functions = 'test_*'
|
||||||
testpaths = [
|
testpaths = [
|
||||||
"tests",
|
"tests",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
skip_empty = true
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue