Release v0.1.0 Candidate (#85)

* Changed uvicorn port to 80

* Changed port in docker-compose to match dockerfile

* Readded environment variables in docker-compose

* production image rework

* Use opengraph metadata to make basic recipe cards when full recipe metadata is not available

* fixed instrucitons on parse

* add last_recipe

* automated testing

* roadmap update

* Sqlite (#75)

* file structure

* auto-test

* take 2

* refactor ap scheduler and startup process

* fixed scraper error

* database abstraction

* database abstraction

* port recipes over to new schema

* meal migration

* start settings migration

* finale mongo port

* backup improvements

* migration imports to new DB structure

* unused import cleanup

* docs strings

* settings and theme import logic

* cleanup

* fixed tinydb error

* requirements

* fuzzy search

* remove scratch file

* sqlalchemy models

* improved search ui

* recipe models almost done

* sql modal population

* del scratch

* rewrite database model mixins

* mostly grabage

* recipe updates

* working sqllite

* remove old files and reorganize

* final cleanup

Co-authored-by: Hayden <hay-kot@pm.me>

* Backup card (#78)

* backup / import dialog

* upgrade to new tag method

* New import card

* rename settings.py to app_config.py

* migrate to poetry for development

* fix failing test

Co-authored-by: Hayden <hay-kot@pm.me>

* added mkdocs to docker-compose

* Translations (#72)

* Translations + danish

* changed back proxy target to use ENV

* Resolved more merge conflicts

* Removed test in translation

* Documentation of translations

* Updated translations

* removed old packages

Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>

* fail to start bug fixes

* feature: prep/cook/total time slots (#80)

Co-authored-by: Hayden <hay-kot@pm.me>

* missing bind attributes

* Bug fixes (#81)

* fix: url remains after succesful import

* docs: changelog + update todos

* arm image

* arm compose

* compose updates

* update poetry

* arm support

Co-authored-by: Hayden <hay-kot@pm.me>

* dockerfile hotfix

* dockerfile hotfix

* Version Release Final Touches (#84)

* Remove slim

* bug: opacity issues

* bug: startup failure with no database

* ci/cd on dev branch

* formatting

* v0.1.0 documentation

Co-authored-by: Hayden <hay-kot@pm.me>

* db init hotfix

* bug: fix crash in mongo

* fix mongo bug

* fixed version notifier

* finale changelog

Co-authored-by: kentora <=>
Co-authored-by: Hayden <hay-kot@pm.me>
Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com>
Co-authored-by: kentora <kentora@kentora.dk>
This commit is contained in:
Hayden
2021-01-17 22:22:54 -09:00
committed by GitHub
parent f6c1fa0e8b
commit 88dfd40b8d
173 changed files with 10273 additions and 3735 deletions

15
mealie/db/database.py Normal file
View File

@@ -0,0 +1,15 @@
from db.db_mealplan import _Meals
from db.db_recipes import _Recipes
from db.db_settings import _Settings
from db.db_themes import _Themes
class Database:
def __init__(self) -> None:
self.recipes = _Recipes()
self.meals = _Meals()
self.settings = _Settings()
self.themes = _Themes()
db = Database()

191
mealie/db/db_base.py Normal file
View File

@@ -0,0 +1,191 @@
import json
from typing import Union
import mongoengine
from app_config import USE_MONGO, USE_SQL
from sqlalchemy.orm.session import Session
from db.sql.db_session import create_session
from db.sql.model_base import SqlAlchemyBase
class BaseDocument:
def __init__(self) -> None:
self.primary_key: str
self.store: str
self.document: mongoengine.Document
self.sql_model: SqlAlchemyBase
self.create_session = create_session
@staticmethod # TODO: Probably Put a version in each class to speed up reads?
def _unpack_mongo(document) -> dict:
document = json.loads(document.to_json())
del document["_id"]
# Recipe Cleanup
try:
document["dateAdded"] = document["dateAdded"]["$date"]
except:
pass
try:
document["uid"] = document["uid"]["$uuid"]
except:
pass
# Meal Plan
try:
document["startDate"] = document["startDate"]["$date"]
document["endDate"] = document["endDate"]["$date"]
meals = []
for meal in document["meals"]:
meal["date"] = meal["date"]["$date"]
meals.append(meal)
document["meals"] = meals
except:
pass
return document
def get_all(self, limit: int = None, order_by: str = None):
if USE_MONGO:
if order_by:
documents = self.document.objects.order_by(str(order_by)).limit(limit)
elif limit == None:
documents = self.document.objects()
else:
documents = self.document.objects().limit(limit)
docs = [BaseDocument._unpack_mongo(item) for item in documents]
if limit == 1:
return docs[0]
return docs
elif USE_SQL:
session = create_session()
list = [x.dict() for x in session.query(self.sql_model).all()]
session.close()
if limit == 1:
return list[0]
return list
def _query_one(
self, match_value: str, match_key: str = None
) -> Union[Session, SqlAlchemyBase]:
"""Query the sql database for one item an return the sql alchemy model
object. If no match key is provided the primary_key attribute will be used.
Args:
match_value (str): The value to use in the query
match_key (str, optional): the key/property to match against. Defaults to None.
Returns:
Union[Session, SqlAlchemyBase]: Will return both the session and found model
"""
session = self.create_session()
if match_key == None:
match_key = self.primary_key
result = (
session.query(self.sql_model).filter_by(**{match_key: match_value}).one()
)
return session, result
def get(
self, match_value: str, match_key: str = None, limit=1
) -> dict or list[dict]:
"""Retrieves an entry from the database by matching a key/value pair. If no
key is provided the class objects primary key will be used to match against.
Args: \n
match_value (str): A value used to match against the key/value in the database \n
match_key (str, optional): They key to match the value against. Defaults to None. \n
limit (int, optional): A limit to returned responses. Defaults to 1. \n
Returns:
dict or list[dict]:
"""
if match_key == None:
match_key = self.primary_key
if USE_MONGO:
document = self.document.objects.get(**{str(match_key): match_value})
db_entry = BaseDocument._unpack_mongo(document)
elif USE_SQL:
session = self.create_session()
result = (
session.query(self.sql_model)
.filter_by(**{match_key: match_value})
.one()
)
db_entry = result.dict()
session.close()
return db_entry
else:
raise Exception("No database type established")
if limit == 1 and type(db_entry) == list:
return db_entry[0]
else:
return db_entry
def save_new(self, document: dict) -> dict:
if USE_MONGO:
new_document = self.document(**document)
new_document.save()
return BaseDocument._unpack_mongo(new_document)
elif USE_SQL:
session = self.create_session()
new_document = self.sql_model(**document)
session.add(new_document)
return_data = new_document.dict()
session.commit()
return return_data
def update(self, match_value, new_data) -> dict:
if USE_MONGO:
return_data = self.update_mongo(match_value, new_data)
elif USE_SQL:
session, entry = self._query_one(match_value=match_value)
entry.update(session=session, **new_data)
return_data = entry.dict()
session.commit()
session.close()
else:
raise Exception("No Database Configured")
return return_data
def delete(self, primary_key_value) -> dict:
if USE_MONGO:
document = self.document.objects.get(
**{str(self.primary_key): primary_key_value}
)
if document:
document.delete()
elif USE_SQL:
session = create_session()
result = (
session.query(self.sql_model)
.filter_by(**{self.primary_key: primary_key_value})
.one()
)
session.delete(result)
session.commit()
session.close()

63
mealie/db/db_mealplan.py Normal file
View File

@@ -0,0 +1,63 @@
from typing import List
from app_config import USE_MONGO, USE_SQL
from db.db_base import BaseDocument
from db.db_setup import USE_MONGO, USE_SQL
from db.mongo.meal_models import MealDocument, MealPlanDocument
from db.sql.db_session import create_session
from db.sql.meal_models import MealPlanModel
class _Meals(BaseDocument):
def __init__(self) -> None:
self.primary_key = "uid"
if USE_SQL:
self.sql_model = MealPlanModel
self.create_session = create_session
self.document = MealPlanDocument
@staticmethod
def _process_meals(meals: List[dict]) -> List[MealDocument]:
"""Turns a list of Meals in dictionary form into a list of
MealDocuments that can be attached to a MealPlanDocument
Args: \n
meals (List[dict]): From a Pydantic Class in meal_services.py \n
Returns:
a List of MealDocuments
"""
meal_docs = []
for meal in meals:
meal_doc = MealDocument(**meal)
meal_docs.append(meal_doc)
return meal_docs
def save_new_mongo(self, plan_data: dict) -> None:
"""Saves a new meal plan into the database
Args: \n
plan_data (dict): From a Pydantic Class in meal_services.py \n
"""
if USE_MONGO:
plan_data["meals"] = _Meals._process_meals(plan_data["meals"])
document = self.document(**plan_data)
document.save()
elif USE_SQL:
pass
def update_mongo(self, uid: str, plan_data: dict) -> dict:
if USE_MONGO:
document = self.document.objects.get(uid=uid)
if document:
new_meals = _Meals._process_meals(plan_data["meals"])
document.update(set__meals=new_meals)
document.save()
elif USE_SQL:
pass

68
mealie/db/db_recipes.py Normal file
View File

@@ -0,0 +1,68 @@
from app_config import USE_MONGO, USE_SQL
from db.db_base import BaseDocument
from db.mongo.recipe_models import RecipeDocument
from db.sql.db_session import create_session
from db.sql.recipe_models import RecipeModel
class _Recipes(BaseDocument):
def __init__(self) -> None:
self.primary_key = "slug"
if USE_SQL:
self.sql_model = RecipeModel
self.create_session = create_session
else:
self.document = RecipeDocument
def save_new_sql(self, recipe_data: dict):
session = self.create_session()
new_recipe = self.sql_model(**recipe_data)
session.add(new_recipe)
session.commit()
return recipe_data
def update_mongo(self, slug: str, new_data: dict) -> None:
if USE_MONGO:
document = self.document.objects.get(slug=slug)
if document:
document.update(set__name=new_data.get("name"))
document.update(set__description=new_data.get("description"))
document.update(set__image=new_data.get("image"))
document.update(set__recipeYield=new_data.get("recipeYield"))
document.update(set__recipeIngredient=new_data.get("recipeIngredient"))
document.update(
set__recipeInstructions=new_data.get("recipeInstructions")
)
document.update(set__totalTime=new_data.get("totalTime"))
document.update(set__slug=new_data.get("slug"))
document.update(set__categories=new_data.get("categories"))
document.update(set__tags=new_data.get("tags"))
document.update(set__notes=new_data.get("notes"))
document.update(set__orgURL=new_data.get("orgURL"))
document.update(set__rating=new_data.get("rating"))
document.update(set__extras=new_data.get("extras"))
document.save()
return new_data
# elif USE_SQL:
# session, recipe = self._query_one(match_value=slug)
# recipe.update(session=session, **new_data)
# recipe_dict = recipe.dict()
# session.commit()
# session.close()
# return recipe_dict
def update_image(self, slug: str, extension: str) -> None:
if USE_MONGO:
document = self.document.objects.get(slug=slug)
if document:
document.update(set__image=f"{slug}.{extension}")
elif USE_SQL:
pass

44
mealie/db/db_settings.py Normal file
View File

@@ -0,0 +1,44 @@
from app_config import USE_MONGO, USE_SQL
from db.db_base import BaseDocument
from db.db_setup import USE_MONGO, USE_SQL
from db.mongo.settings_models import SiteSettingsDocument, WebhooksDocument
from db.sql.db_session import create_session
from db.sql.settings_models import SiteSettingsModel
class _Settings(BaseDocument):
def __init__(self) -> None:
self.primary_key = "name"
if USE_SQL:
self.sql_model = SiteSettingsModel
self.create_session = create_session
self.document = SiteSettingsDocument
def save_new(self, main: dict, webhooks: dict) -> str:
if USE_MONGO:
main["webhooks"] = WebhooksDocument(**webhooks)
new_doc = self.document(**main)
return new_doc.save()
elif USE_SQL:
session = create_session()
new_settings = self.sql_model(main.get("name"), webhooks)
session.add(new_settings)
session.commit()
return new_settings.dict()
def update_mongo(self, name: str, new_data: dict) -> dict:
if USE_MONGO:
document = self.document.objects.get(name=name)
if document:
document.update(set__webhooks=WebhooksDocument(**new_data["webhooks"]))
document.save()
elif USE_SQL:
return

16
mealie/db/db_setup.py Normal file
View File

@@ -0,0 +1,16 @@
from app_config import SQLITE_FILE, USE_MONGO, USE_SQL
from db.sql.db_session import globa_init as sql_global_init
sql_exists = True
if USE_SQL:
sql_exists = SQLITE_FILE.is_file()
sql_global_init(SQLITE_FILE)
pass
elif USE_MONGO:
from db.mongo.mongo_setup import global_init as mongo_global_init
mongo_global_init()

56
mealie/db/db_themes.py Normal file
View File

@@ -0,0 +1,56 @@
from app_config import USE_MONGO, USE_SQL
from db.db_base import BaseDocument
from db.db_setup import USE_MONGO, USE_SQL
from db.mongo.settings_models import SiteThemeDocument, ThemeColorsDocument
from db.sql.db_session import create_session
from db.sql.theme_models import SiteThemeModel
class _Themes(BaseDocument):
def __init__(self) -> None:
self.primary_key = "name"
if USE_SQL:
self.sql_model = SiteThemeModel
self.create_session = create_session
else:
self.document = SiteThemeDocument
def save_new(self, theme_data: dict) -> None:
if USE_MONGO:
theme_data["colors"] = ThemeColorsDocument(**theme_data["colors"])
document = self.document(**theme_data)
document.save()
elif USE_SQL:
session = self.create_session()
new_theme = self.sql_model(**theme_data)
session.add(new_theme)
session.commit()
return_data = new_theme.dict()
session.close()
return return_data
def update(self, data: dict) -> dict:
if USE_MONGO:
colors = ThemeColorsDocument(**data["colors"])
theme_document = self.document.objects.get(name=data.get("name"))
if theme_document:
theme_document.update(set__colors=colors)
theme_document.save()
else:
raise Exception("No database entry was found to update")
elif USE_SQL:
session, theme_model = self._query_one(
match_value=data["name"], match_key="name"
)
theme_model.update(**data)
session.commit()
session.close()

View File

@@ -1,5 +1,6 @@
import mongoengine
from settings import DB_HOST, DB_PASSWORD, DB_PORT, DB_USERNAME, MEALIE_DB_NAME
from app_config import DB_HOST, DB_PASSWORD, DB_PORT, DB_USERNAME, MEALIE_DB_NAME
from utils.logger import logger
def global_init():
@@ -12,3 +13,5 @@ def global_init():
password=DB_PASSWORD,
authentication_source="admin",
)
logger.info("Mongo Data Initialized")

View File

@@ -1,5 +1,4 @@
import datetime
import uuid
import mongoengine
@@ -19,7 +18,7 @@ class RecipeDocument(mongoengine.Document):
slug = mongoengine.StringField(required=True, unique=True)
categories = mongoengine.ListField(default=[])
tags = mongoengine.ListField(default=[])
dateAdded = mongoengine.DateTimeField(binary=True, default=datetime.date.today())
dateAdded = mongoengine.DateTimeField(binary=True, default=datetime.date.today)
notes = mongoengine.ListField(default=[])
rating = mongoengine.IntField(required=True, default=0)
orgURL = mongoengine.URLField(required=False)

View File

@@ -0,0 +1,6 @@
# import mongoengine
# class User(mongoengine.Document):
# username: mongoengine.EmailField()
# password: mongoengine.ReferenceField

View File

@@ -0,0 +1,4 @@
from db.sql.meal_models import *
from db.sql.recipe_models import *
from db.sql.settings_models import *
from db.sql.theme_models import *

View File

@@ -0,0 +1,29 @@
from pathlib import Path
import sqlalchemy as sa
import sqlalchemy.orm as orm
from db.sql.model_base import SqlAlchemyBase
from sqlalchemy.orm.session import Session
__factory = None
def globa_init(db_file: Path):
global __factory
if __factory:
return
conn_str = "sqlite:///" + str(db_file.absolute())
engine = sa.create_engine(conn_str, echo=False)
__factory = orm.sessionmaker(bind=engine)
import db.sql._all_models
SqlAlchemyBase.metadata.create_all(engine)
def create_session() -> Session:
global __factory
return __factory()

View File

@@ -0,0 +1,66 @@
import uuid
from typing import List
import sqlalchemy as sa
import sqlalchemy.orm as orm
from db.sql.model_base import BaseMixins, SqlAlchemyBase
class Meal(SqlAlchemyBase):
__tablename__ = "meal"
id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("mealplan.uid"))
slug = sa.Column(sa.String)
name = sa.Column(sa.String)
date = sa.Column(sa.Date)
dateText = sa.Column(sa.String)
image = sa.Column(sa.String)
description = sa.Column(sa.String)
def __init__(self, slug, name, date, dateText, image, description) -> None:
self.slug = slug
self.name = name
self.date = date
self.dateText = dateText
self.image = image
self.description = description
def dict(self) -> dict:
data = {
"slug": self.slug,
"name": self.name,
"date": self.date,
"dateText": self.dateText,
"image": self.image,
"description": self.description,
}
return data
class MealPlanModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "mealplan"
uid = sa.Column(sa.Integer, primary_key=True, unique=True) #! Probably Bad?
startDate = sa.Column(sa.Date)
endDate = sa.Column(sa.Date)
meals: List[Meal] = orm.relation(Meal)
def __init__(self, startDate, endDate, meals, uid=None) -> None:
self.startDate = startDate
self.endDate = endDate
self.meals = [Meal(**meal) for meal in meals]
def update(self, session, startDate, endDate, meals, uid) -> None:
MealPlanModel._sql_remove_list(session, [Meal], uid)
self.__init__(startDate, endDate, meals)
def dict(self) -> dict:
data = {
"uid": self.uid,
"startDate": self.startDate,
"endDate": self.endDate,
"meals": [meal.dict() for meal in self.meals],
}
return data

View File

@@ -0,0 +1,22 @@
from typing import List
import sqlalchemy.ext.declarative as dec
SqlAlchemyBase = dec.declarative_base()
class BaseMixins:
@staticmethod
def _sql_remove_list(session, list_of_tables: list, parent_id):
for table in list_of_tables:
session.query(table).filter_by(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

View File

@@ -0,0 +1,244 @@
import datetime
from datetime import date
from typing import List
import sqlalchemy as sa
import sqlalchemy.orm as orm
from db.sql.model_base import BaseMixins, SqlAlchemyBase
from sqlalchemy.ext.orderinglist import ordering_list
class ApiExtras(SqlAlchemyBase):
__tablename__ = "api_extras"
id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
key_name = sa.Column(sa.String, unique=True)
value = sa.Column(sa.String)
def __init__(self, key, value) -> None:
self.key_name = key
self.value = value
def dict(self):
return {self.key_name: self.value}
class Category(SqlAlchemyBase):
__tablename__ = "categories"
id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
name = sa.Column(sa.String, index=True)
def to_str(self):
return self.name
class Tag(SqlAlchemyBase):
__tablename__ = "tags"
id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
name = sa.Column(sa.String, index=True)
def to_str(self):
return self.name
class Note(SqlAlchemyBase):
__tablename__ = "notes"
id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
title = sa.Column(sa.String)
text = sa.Column(sa.String)
def dict(self):
return {"title": self.title, "text": self.text}
class RecipeIngredient(SqlAlchemyBase):
__tablename__ = "recipes_ingredients"
id = sa.Column(sa.Integer, primary_key=True)
position = sa.Column(sa.Integer)
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
ingredient = sa.Column(sa.String)
def update(self, ingredient):
self.ingredient = ingredient
def to_str(self):
return self.ingredient
class RecipeInstruction(SqlAlchemyBase):
__tablename__ = "recipe_instructions"
id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
position = sa.Column(sa.Integer)
type = sa.Column(sa.String, default="")
text = sa.Column(sa.String)
def dict(self):
data = {"@type": self.type, "text": self.text}
return data
class RecipeModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "recipes"
# Database Specific
id = sa.Column(sa.Integer, primary_key=True)
# General Recipe Properties
name = sa.Column(sa.String)
description = sa.Column(sa.String)
image = sa.Column(sa.String)
recipeYield = sa.Column(sa.String)
recipeIngredient: List[RecipeIngredient] = orm.relationship(
"RecipeIngredient",
cascade="all, delete",
order_by="RecipeIngredient.position",
collection_class=ordering_list("position"),
)
recipeInstructions: List[RecipeInstruction] = orm.relationship(
"RecipeInstruction",
cascade="all, delete",
order_by="RecipeInstruction.position",
collection_class=ordering_list("position"),
)
# How to Properties
totalTime = sa.Column(sa.String)
prepTime = sa.Column(sa.String)
performTime = sa.Column(sa.String)
# Mealie Specific
slug = sa.Column(sa.String, index=True, unique=True)
categories: List[Category] = orm.relationship(
"Category",
cascade="all, delete",
)
tags: List[Tag] = orm.relationship(
"Tag",
cascade="all, delete",
)
dateAdded = sa.Column(sa.Date, default=date.today)
notes: List[Note] = orm.relationship(
"Note",
cascade="all, delete",
)
rating = sa.Column(sa.Integer)
orgURL = sa.Column(sa.String)
extras: List[ApiExtras] = orm.relationship("ApiExtras", cascade="all, delete")
def __init__(
self,
name: str = None,
description: str = None,
image: str = None,
recipeYield: str = None,
recipeIngredient: List[str] = None,
recipeInstructions: List[dict] = None,
totalTime: str = None,
prepTime: str = None,
performTime: str = None,
slug: str = None,
categories: List[str] = None,
tags: List[str] = None,
dateAdded: datetime.date = None,
notes: List[dict] = None,
rating: int = None,
orgURL: str = None,
extras: dict = None,
) -> None:
self.name = name
self.description = description
self.image = image
self.recipeYield = recipeYield
self.recipeIngredient = [
RecipeIngredient(ingredient=ingr) for ingr in recipeIngredient
]
self.recipeInstructions = [
RecipeInstruction(text=instruc.get("text"), type=instruc.get("text"))
for instruc in recipeInstructions
]
self.totalTime = totalTime
self.prepTime = prepTime
self.performTime = performTime
# Mealie Specific
self.slug = slug
self.categories = [Category(name=cat) for cat in categories]
self.tags = [Tag(name=tag) for tag in tags]
self.dateAdded = dateAdded
self.notes = [Note(note) for note in notes]
self.rating = rating
self.orgURL = orgURL
self.extras = [ApiExtras(key=key, value=value) for key, value in extras.items()]
def update(
self,
session,
name: str = None,
description: str = None,
image: str = None,
recipeYield: str = None,
recipeIngredient: List[str] = None,
recipeInstructions: List[dict] = None,
totalTime: str = None,
prepTime: str = None,
performTime: str = None,
slug: str = None,
categories: List[str] = None,
tags: List[str] = None,
dateAdded: datetime.date = None,
notes: List[dict] = None,
rating: int = None,
orgURL: str = None,
extras: dict = None,
):
"""Updated a database entry by removing nested rows and rebuilds the row through the __init__ functions"""
list_of_tables = [RecipeIngredient, RecipeInstruction, Category, Tag, ApiExtras]
RecipeModel._sql_remove_list(session, list_of_tables, self.id)
self.__init__(
name=name,
description=description,
image=image,
recipeYield=recipeYield,
recipeIngredient=recipeIngredient,
recipeInstructions=recipeInstructions,
totalTime=totalTime,
prepTime=prepTime,
performTime=performTime,
slug=slug,
categories=categories,
tags=tags,
dateAdded=dateAdded,
notes=notes,
rating=rating,
orgURL=orgURL,
extras=extras,
)
def dict(self):
data = {
"name": self.name,
"description": self.description,
"image": self.image,
"recipeYield": self.recipeYield,
"recipeIngredient": [x.to_str() for x in self.recipeIngredient],
"recipeInstructions": [x.dict() for x in self.recipeInstructions],
"totalTime": self.totalTime,
"prepTime": self.prepTime,
"performTime": self.performTime,
# Mealie
"slug": self.slug,
"categories": [x.to_str() for x in self.categories],
"tags": [x.to_str() for x in self.tags],
"dateAdded": self.dateAdded,
"notes": [x.dict() for x in self.notes],
"rating": self.rating,
"orgURL": self.orgURL,
"extras": RecipeModel._flatten_dict(self.extras),
}
return data

View File

@@ -0,0 +1,67 @@
import sqlalchemy as sa
import sqlalchemy.orm as orm
from db.sql.model_base import BaseMixins, SqlAlchemyBase
class SiteSettingsModel(SqlAlchemyBase):
__tablename__ = "site_settings"
name = sa.Column(sa.String, primary_key=True)
webhooks = orm.relationship("WebHookModel", uselist=False, cascade="all, delete")
def __init__(self, name: str = None, webhooks: dict = None) -> None:
self.name = name
self.webhooks = WebHookModel(**webhooks)
def update(self, session, name, webhooks: dict) -> dict:
self.name = name
self.webhooks.update(session=session, **webhooks)
return
def dict(self):
data = {"name": self.name, "webhooks": self.webhooks.dict()}
return data
class WebHookModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "webhook_settings"
id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("site_settings.name"))
webhookURLs = orm.relationship(
"WebhookURLModel", uselist=True, cascade="all, delete"
)
webhookTime = sa.Column(sa.String, default="00:00")
enabled = sa.Column(sa.Boolean, default=False)
def __init__(
self, webhookURLs: list, webhookTime: str, enabled: bool = False
) -> None:
self.webhookURLs = [WebhookURLModel(url=x) for x in webhookURLs]
self.webhookTime = webhookTime
self.enabled = enabled
def update(
self, session, webhookURLs: list, webhookTime: str, enabled: bool
) -> None:
self._sql_remove_list(session, [WebhookURLModel], self.id)
self.__init__(webhookURLs, webhookTime, enabled)
def dict(self):
data = {
"webhookURLs": [url.to_str() for url in self.webhookURLs],
"webhookTime": self.webhookTime,
"enabled": self.enabled,
}
return data
class WebhookURLModel(SqlAlchemyBase):
__tablename__ = "webhook_urls"
id = sa.Column(sa.Integer, primary_key=True)
url = sa.Column(sa.String)
parent_id = sa.Column(sa.Integer, sa.ForeignKey("webhook_settings.id"))
def to_str(self):
return self.url

View File

@@ -0,0 +1,64 @@
import sqlalchemy as sa
import sqlalchemy.orm as orm
from db.sql.model_base import BaseMixins, SqlAlchemyBase
class SiteThemeModel(SqlAlchemyBase):
__tablename__ = "site_theme"
name = sa.Column(sa.String, primary_key=True)
colors = orm.relationship("ThemeColorsModel", uselist=False, cascade="all, delete")
def __init__(self, name: str, colors: dict) -> None:
self.name = name
self.colors = ThemeColorsModel(**colors)
def update(self, name, colors: dict) -> dict:
self.colors.update(**colors)
return self.dict()
def dict(self):
data = {"name": self.name, "colors": self.colors.dict()}
return data
class ThemeColorsModel(SqlAlchemyBase):
__tablename__ = "theme_colors"
id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("site_theme.name"))
primary = sa.Column(sa.String)
accent = sa.Column(sa.String)
secondary = sa.Column(sa.String)
success = sa.Column(sa.String)
info = sa.Column(sa.String)
warning = sa.Column(sa.String)
error = sa.Column(sa.String)
def update(
self,
primary: str = None,
accent: str = None,
secondary: str = None,
success: str = None,
info: str = None,
warning: str = None,
error: str = None,
) -> None:
self.primary = primary
self.accent = accent
self.secondary = secondary
self.success = success
self.info = info
self.warning = warning
self.error = error
def dict(self):
data = {
"primary": self.primary,
"accent": self.accent,
"secondary": self.secondary,
"success": self.success,
"info": self.info,
"warning": self.warning,
"error": self.error,
}
return data

View File

@@ -1,6 +0,0 @@
import mongoengine
class User(mongoengine.Document):
username: mongoengine.EmailField()
# password: mongoengine.ReferenceField