mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-21 09:45:18 -05:00
category/tag database relationship and endpoints
This commit is contained in:
@@ -8,16 +8,32 @@ from routes import (
|
|||||||
backup_routes,
|
backup_routes,
|
||||||
meal_routes,
|
meal_routes,
|
||||||
migration_routes,
|
migration_routes,
|
||||||
recipe_routes,
|
|
||||||
setting_routes,
|
setting_routes,
|
||||||
static_routes,
|
static_routes,
|
||||||
user_routes,
|
user_routes,
|
||||||
category_routes,
|
|
||||||
)
|
)
|
||||||
|
from routes.recipe import (
|
||||||
|
all_recipe_routes,
|
||||||
|
category_routes,
|
||||||
|
recipe_crud_routes,
|
||||||
|
tag_routes,
|
||||||
|
)
|
||||||
from utils.api_docs import generate_api_docs
|
from utils.api_docs import generate_api_docs
|
||||||
from utils.logger import logger
|
from utils.logger import logger
|
||||||
|
|
||||||
|
"""
|
||||||
|
TODO:
|
||||||
|
- [ ] Fix Duplicate Category
|
||||||
|
- [ ] Fix Duplicate Tags
|
||||||
|
- [ ] Add Endpoint
|
||||||
|
- [ ] Endpoint Tests
|
||||||
|
- [ ] Setup Database Migrations
|
||||||
|
- [ ] Finish Frontend Category Management
|
||||||
|
- [ ] Ingredient Drag-Drop / Reorder
|
||||||
|
- [ ] Refactor Endpoints
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="Mealie",
|
title="Mealie",
|
||||||
description="A place for all your recipes",
|
description="A place for all your recipes",
|
||||||
@@ -32,15 +48,21 @@ def mount_static_files():
|
|||||||
|
|
||||||
|
|
||||||
def api_routers():
|
def api_routers():
|
||||||
# First
|
# Recipes
|
||||||
print()
|
app.include_router(all_recipe_routes.router)
|
||||||
app.include_router(recipe_routes.router)
|
app.include_router(recipe_crud_routes.router)
|
||||||
app.include_router(meal_routes.router)
|
|
||||||
app.include_router(setting_routes.router)
|
|
||||||
app.include_router(backup_routes.router)
|
|
||||||
app.include_router(user_routes.router)
|
|
||||||
app.include_router(migration_routes.router)
|
|
||||||
app.include_router(category_routes.router)
|
app.include_router(category_routes.router)
|
||||||
|
app.include_router(tag_routes.router)
|
||||||
|
# Meal Routes
|
||||||
|
app.include_router(meal_routes.router)
|
||||||
|
# Settings Routes
|
||||||
|
app.include_router(setting_routes.router)
|
||||||
|
# Backups/Imports Routes
|
||||||
|
app.include_router(backup_routes.router)
|
||||||
|
# User Routes
|
||||||
|
app.include_router(user_routes.router)
|
||||||
|
# Migration Routes
|
||||||
|
app.include_router(migration_routes.router)
|
||||||
|
|
||||||
|
|
||||||
if PRODUCTION:
|
if PRODUCTION:
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ from sqlalchemy.orm.session import Session
|
|||||||
|
|
||||||
from db.db_base import BaseDocument
|
from db.db_base import BaseDocument
|
||||||
from db.sql.meal_models import MealPlanModel
|
from db.sql.meal_models import MealPlanModel
|
||||||
from db.sql.recipe_models import RecipeModel
|
from db.sql.recipe_models import Category, RecipeModel, Tag
|
||||||
from db.sql.settings_models import SiteSettingsModel
|
from db.sql.settings_models import SiteSettingsModel
|
||||||
from db.sql.theme_models import SiteThemeModel
|
from db.sql.theme_models import SiteThemeModel
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# TODO
|
# TODO
|
||||||
- [ ] Abstract Classes to use save_new, and update from base models
|
- [ ] Abstract Classes to use save_new, and update from base models
|
||||||
- [ ] Create Category and Tags Table with Many to Many relationship
|
- [x] Create Category and Tags Table with Many to Many relationship
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -19,13 +19,25 @@ class _Recipes(BaseDocument):
|
|||||||
self.sql_model = RecipeModel
|
self.sql_model = RecipeModel
|
||||||
|
|
||||||
def update_image(self, session: Session, slug: str, extension: str) -> str:
|
def update_image(self, session: Session, slug: str, extension: str) -> str:
|
||||||
entry = self._query_one(session, match_value=slug)
|
entry: RecipeModel = self._query_one(session, match_value=slug)
|
||||||
entry.image = f"{slug}.{extension}"
|
entry.image = f"{slug}.{extension}"
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
return f"{slug}.{extension}"
|
return f"{slug}.{extension}"
|
||||||
|
|
||||||
|
|
||||||
|
class _Categories(BaseDocument):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.primary_key = "name"
|
||||||
|
self.sql_model = Category
|
||||||
|
|
||||||
|
|
||||||
|
class _Tags(BaseDocument):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.primary_key = "name"
|
||||||
|
self.sql_model = Tag
|
||||||
|
|
||||||
|
|
||||||
class _Meals(BaseDocument):
|
class _Meals(BaseDocument):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.primary_key = "uid"
|
self.primary_key = "uid"
|
||||||
@@ -58,6 +70,8 @@ class Database:
|
|||||||
self.meals = _Meals()
|
self.meals = _Meals()
|
||||||
self.settings = _Settings()
|
self.settings = _Settings()
|
||||||
self.themes = _Themes()
|
self.themes = _Themes()
|
||||||
|
self.categories = _Categories()
|
||||||
|
self.tags = _Tags()
|
||||||
|
|
||||||
|
|
||||||
db = Database()
|
db = Database()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from typing import List, Union
|
from typing import List
|
||||||
|
|
||||||
|
from sqlalchemy.orm import load_only
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
from db.sql.model_base import SqlAlchemyBase
|
from db.sql.model_base import SqlAlchemyBase
|
||||||
@@ -22,6 +23,13 @@ class BaseDocument:
|
|||||||
|
|
||||||
return list
|
return list
|
||||||
|
|
||||||
|
def get_all_primary_keys(self, session: Session):
|
||||||
|
results = session.query(self.sql_model).options(
|
||||||
|
load_only(str(self.primary_key))
|
||||||
|
)
|
||||||
|
results_as_dict = [x.dict() for x in results]
|
||||||
|
return [x.get(self.primary_key) for x in results_as_dict]
|
||||||
|
|
||||||
def _query_one(
|
def _query_one(
|
||||||
self, session: Session, match_value: str, match_key: str = None
|
self, session: Session, match_value: str, match_key: str = None
|
||||||
) -> SqlAlchemyBase:
|
) -> SqlAlchemyBase:
|
||||||
@@ -79,7 +87,7 @@ class BaseDocument:
|
|||||||
Returns:
|
Returns:
|
||||||
dict: A dictionary representation of the database entry
|
dict: A dictionary representation of the database entry
|
||||||
"""
|
"""
|
||||||
new_document = self.sql_model(**document)
|
new_document = self.sql_model(session=session, **document)
|
||||||
session.add(new_document)
|
session.add(new_document)
|
||||||
return_data = new_document.dict()
|
return_data = new_document.dict()
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ class Meal(SqlAlchemyBase):
|
|||||||
image = sa.Column(sa.String)
|
image = sa.Column(sa.String)
|
||||||
description = sa.Column(sa.String)
|
description = sa.Column(sa.String)
|
||||||
|
|
||||||
def __init__(self, slug, name, date, dateText, image, description) -> None:
|
def __init__(
|
||||||
|
self, slug, name, date, dateText, image, description, session=None
|
||||||
|
) -> None:
|
||||||
self.slug = slug
|
self.slug = slug
|
||||||
self.name = name
|
self.name = name
|
||||||
self.date = date
|
self.date = date
|
||||||
@@ -45,7 +47,7 @@ class MealPlanModel(SqlAlchemyBase, BaseMixins):
|
|||||||
endDate = sa.Column(sa.Date)
|
endDate = sa.Column(sa.Date)
|
||||||
meals: List[Meal] = orm.relation(Meal)
|
meals: List[Meal] = orm.relation(Meal)
|
||||||
|
|
||||||
def __init__(self, startDate, endDate, meals, uid=None) -> None:
|
def __init__(self, startDate, endDate, meals, uid=None, session=None) -> None:
|
||||||
self.startDate = startDate
|
self.startDate = startDate
|
||||||
self.endDate = endDate
|
self.endDate = endDate
|
||||||
self.meals = [Meal(**meal) for meal in meals]
|
self.meals = [Meal(**meal) for meal in meals]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import sqlalchemy as sa
|
|||||||
import sqlalchemy.orm as orm
|
import sqlalchemy.orm as orm
|
||||||
from db.sql.model_base import BaseMixins, SqlAlchemyBase
|
from db.sql.model_base import BaseMixins, SqlAlchemyBase
|
||||||
from sqlalchemy.ext.orderinglist import ordering_list
|
from sqlalchemy.ext.orderinglist import ordering_list
|
||||||
|
from utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class ApiExtras(SqlAlchemyBase):
|
class ApiExtras(SqlAlchemyBase):
|
||||||
@@ -23,36 +24,80 @@ class ApiExtras(SqlAlchemyBase):
|
|||||||
return {self.key_name: self.value}
|
return {self.key_name: self.value}
|
||||||
|
|
||||||
|
|
||||||
recipes2categories = sa.Table("recipes2categories", SqlAlchemyBase.metadata,
|
recipes2categories = sa.Table(
|
||||||
|
"recipes2categories",
|
||||||
|
SqlAlchemyBase.metadata,
|
||||||
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
||||||
sa.Column("category_id", sa.Integer, sa.ForeignKey("categories.id")))
|
sa.Column("category_name", sa.String, sa.ForeignKey("categories.name")),
|
||||||
|
)
|
||||||
|
|
||||||
|
recipes2tags = sa.Table(
|
||||||
|
"recipes2tags",
|
||||||
|
SqlAlchemyBase.metadata,
|
||||||
|
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
||||||
|
sa.Column("tag_id", sa.Integer, sa.ForeignKey("tags.id")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Category(SqlAlchemyBase):
|
class Category(SqlAlchemyBase):
|
||||||
__tablename__ = "categories"
|
__tablename__ = "categories"
|
||||||
id = sa.Column(sa.Integer, primary_key=True)
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
name = sa.Column(sa.String, index=True)
|
name = sa.Column(sa.String, index=True)
|
||||||
recipes = orm.relationship(
|
recipes = orm.relationship(
|
||||||
"RecipeModel",
|
"RecipeModel", secondary=recipes2categories, back_populates="categories"
|
||||||
secondary=recipes2categories,
|
|
||||||
back_populates="categories"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, name) -> None:
|
def __init__(self, name) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_if_not_exist(cls, session, name: str):
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = session.query(Category).filter_by(**{"name": name}).one()
|
||||||
|
logger.info("Category Exists, Associating Recipe")
|
||||||
|
|
||||||
|
return result
|
||||||
|
except:
|
||||||
|
logger.info("Category doesn't exists, creating category")
|
||||||
|
return cls(name=name)
|
||||||
|
|
||||||
def to_str(self):
|
def to_str(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def dict(self):
|
||||||
|
return {"id": self.id, "name": self.name, "recipes": [x.dict() for x in self.recipes]}
|
||||||
|
|
||||||
|
|
||||||
class Tag(SqlAlchemyBase):
|
class Tag(SqlAlchemyBase):
|
||||||
__tablename__ = "tags"
|
__tablename__ = "tags"
|
||||||
id = sa.Column(sa.Integer, primary_key=True)
|
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)
|
name = sa.Column(sa.String, index=True)
|
||||||
|
recipes = orm.relationship(
|
||||||
|
"RecipeModel", secondary=recipes2tags, back_populates="tags"
|
||||||
|
)
|
||||||
|
|
||||||
def to_str(self):
|
def to_str(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def __init__(self, name) -> None:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def dict(self):
|
||||||
|
return {"id": self.id, "name": self.name, "recipes": [x.dict() for x in self.recipes]}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_if_not_exist(cls, session, name: str):
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = session.query(Tag).filter_by(**{"name": name}).one()
|
||||||
|
logger.info("Tag Exists, Associating Recipe")
|
||||||
|
|
||||||
|
return result
|
||||||
|
except:
|
||||||
|
logger.info("Tag doesn't exists, creating tag")
|
||||||
|
return cls(name=name)
|
||||||
|
|
||||||
|
|
||||||
class Note(SqlAlchemyBase):
|
class Note(SqlAlchemyBase):
|
||||||
__tablename__ = "notes"
|
__tablename__ = "notes"
|
||||||
@@ -128,25 +173,20 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||||||
# Mealie Specific
|
# Mealie Specific
|
||||||
slug = sa.Column(sa.String, index=True, unique=True)
|
slug = sa.Column(sa.String, index=True, unique=True)
|
||||||
categories: List = orm.relationship(
|
categories: List = orm.relationship(
|
||||||
"Category",
|
"Category", secondary=recipes2categories, back_populates="recipes"
|
||||||
secondary=recipes2categories,
|
|
||||||
back_populates="recipes",
|
|
||||||
)
|
)
|
||||||
tags: List[Tag] = orm.relationship(
|
tags: List[Tag] = orm.relationship(
|
||||||
"Tag",
|
"Tag", secondary=recipes2tags, back_populates="recipes"
|
||||||
cascade="all, delete",
|
|
||||||
)
|
)
|
||||||
dateAdded = sa.Column(sa.Date, default=date.today)
|
dateAdded = sa.Column(sa.Date, default=date.today)
|
||||||
notes: List[Note] = orm.relationship(
|
notes: List[Note] = orm.relationship("Note", cascade="all, delete")
|
||||||
"Note",
|
|
||||||
cascade="all, delete",
|
|
||||||
)
|
|
||||||
rating = sa.Column(sa.Integer)
|
rating = sa.Column(sa.Integer)
|
||||||
orgURL = sa.Column(sa.String)
|
orgURL = sa.Column(sa.String)
|
||||||
extras: List[ApiExtras] = orm.relationship("ApiExtras", cascade="all, delete")
|
extras: List[ApiExtras] = orm.relationship("ApiExtras", cascade="all, delete")
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
session,
|
||||||
name: str = None,
|
name: str = None,
|
||||||
description: str = None,
|
description: str = None,
|
||||||
image: str = None,
|
image: str = None,
|
||||||
@@ -182,8 +222,12 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||||||
|
|
||||||
# Mealie Specific
|
# Mealie Specific
|
||||||
self.slug = slug
|
self.slug = slug
|
||||||
self.categories = [Category(cat) for cat in categories]
|
|
||||||
self.tags = [Tag(name=tag) for tag in tags]
|
self.categories = [
|
||||||
|
(Category.create_if_not_exist(session, cat)) for cat in categories
|
||||||
|
]
|
||||||
|
self.tags = [Tag.create_if_not_exist(session, name=tag) for tag in tags]
|
||||||
|
|
||||||
self.dateAdded = dateAdded
|
self.dateAdded = dateAdded
|
||||||
self.notes = [Note(**note) for note in notes]
|
self.notes = [Note(**note) for note in notes]
|
||||||
self.rating = rating
|
self.rating = rating
|
||||||
@@ -212,10 +256,11 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||||||
extras: dict = None,
|
extras: dict = None,
|
||||||
):
|
):
|
||||||
"""Updated a database entry by removing nested rows and rebuilds the row through the __init__ functions"""
|
"""Updated a database entry by removing nested rows and rebuilds the row through the __init__ functions"""
|
||||||
list_of_tables = [RecipeIngredient, RecipeInstruction, Tag, ApiExtras]
|
list_of_tables = [RecipeIngredient, RecipeInstruction, ApiExtras]
|
||||||
RecipeModel._sql_remove_list(session, list_of_tables, self.id)
|
RecipeModel._sql_remove_list(session, list_of_tables, self.id)
|
||||||
|
|
||||||
self.__init__(
|
self.__init__(
|
||||||
|
session=session,
|
||||||
name=name,
|
name=name,
|
||||||
description=description,
|
description=description,
|
||||||
image=image,
|
image=image,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class SiteSettingsModel(SqlAlchemyBase):
|
|||||||
name = sa.Column(sa.String, primary_key=True)
|
name = sa.Column(sa.String, primary_key=True)
|
||||||
webhooks = orm.relationship("WebHookModel", uselist=False, cascade="all, delete")
|
webhooks = orm.relationship("WebHookModel", uselist=False, cascade="all, delete")
|
||||||
|
|
||||||
def __init__(self, name: str = None, webhooks: dict = None) -> None:
|
def __init__(self, name: str = None, webhooks: dict = None, session=None) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.webhooks = WebHookModel(**webhooks)
|
self.webhooks = WebHookModel(**webhooks)
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ class WebHookModel(SqlAlchemyBase, BaseMixins):
|
|||||||
enabled = sa.Column(sa.Boolean, default=False)
|
enabled = sa.Column(sa.Boolean, default=False)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, webhookURLs: list, webhookTime: str, enabled: bool = False
|
self, webhookURLs: list, webhookTime: str, enabled: bool = False, session=None
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
self.webhookURLs = [WebhookURLModel(url=x) for x in webhookURLs]
|
self.webhookURLs = [WebhookURLModel(url=x) for x in webhookURLs]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class SiteThemeModel(SqlAlchemyBase):
|
|||||||
name = sa.Column(sa.String, primary_key=True)
|
name = sa.Column(sa.String, primary_key=True)
|
||||||
colors = orm.relationship("ThemeColorsModel", uselist=False, cascade="all, delete")
|
colors = orm.relationship("ThemeColorsModel", uselist=False, cascade="all, delete")
|
||||||
|
|
||||||
def __init__(self, name: str, colors: dict) -> None:
|
def __init__(self, name: str, colors: dict, session=None) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.colors = ThemeColorsModel(**colors)
|
self.colors = ThemeColorsModel(**colors)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
from pydantic.main import BaseModel
|
from typing import List
|
||||||
|
|
||||||
class Category(BaseModel):
|
from pydantic.main import BaseModel
|
||||||
|
from services.recipe_services import Recipe
|
||||||
|
|
||||||
|
|
||||||
|
class RecipeCategoryResponse(BaseModel):
|
||||||
|
id: int
|
||||||
name: str
|
name: str
|
||||||
|
recipes: List[Recipe]
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
schema_extra = {
|
schema_extra = {"example": {"id": 1, "name": "dinner", "recipes": [{}]}}
|
||||||
"example": {
|
|
||||||
"name": "Breakfast"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ from sqlalchemy.orm.session import Session
|
|||||||
from starlette.responses import FileResponse
|
from starlette.responses import FileResponse
|
||||||
from utils.snackbar import SnackResponse
|
from utils.snackbar import SnackResponse
|
||||||
|
|
||||||
router = APIRouter(tags=["Import / Export"])
|
router = APIRouter(prefix="/api/backups", tags=["Import / Export"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/backups/available/", response_model=Imports)
|
@router.get("/available/", response_model=Imports)
|
||||||
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 = []
|
||||||
@@ -31,7 +31,7 @@ def available_imports():
|
|||||||
return Imports(imports=imports, templates=templates)
|
return Imports(imports=imports, templates=templates)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/backups/export/database/", status_code=201)
|
@router.post("/export/database/", status_code=201)
|
||||||
def export_database(data: BackupJob, db: Session = Depends(generate_session)):
|
def export_database(data: BackupJob, db: Session = Depends(generate_session)):
|
||||||
"""Generates a backup of the recipe database in json format."""
|
"""Generates a backup of the recipe database in json format."""
|
||||||
export_path = backup_all(
|
export_path = backup_all(
|
||||||
@@ -51,7 +51,7 @@ def export_database(data: BackupJob, db: Session = Depends(generate_session)):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/backups/upload/")
|
@router.post("/upload/")
|
||||||
def upload_backup_zipfile(archive: UploadFile = File(...)):
|
def upload_backup_zipfile(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 = BACKUP_DIR.joinpath(archive.filename)
|
||||||
@@ -65,7 +65,7 @@ def upload_backup_zipfile(archive: UploadFile = File(...)):
|
|||||||
return SnackResponse.error("Failure uploading file")
|
return SnackResponse.error("Failure uploading file")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/backups/{file_name}/download/")
|
@router.get("/{file_name}/download/")
|
||||||
def upload_nextcloud_zipfile(file_name: str):
|
def upload_nextcloud_zipfile(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 = BACKUP_DIR.joinpath(file_name)
|
||||||
@@ -78,7 +78,7 @@ def upload_nextcloud_zipfile(file_name: str):
|
|||||||
return SnackResponse.error("No File Found")
|
return SnackResponse.error("No File Found")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/backups/{file_name}/import/", status_code=200)
|
@router.post("/{file_name}/import/", status_code=200)
|
||||||
def import_database(
|
def import_database(
|
||||||
file_name: str, import_data: ImportJob, db: Session = Depends(generate_session)
|
file_name: str, import_data: ImportJob, db: Session = Depends(generate_session)
|
||||||
):
|
):
|
||||||
@@ -98,20 +98,16 @@ def import_database(
|
|||||||
return imported
|
return imported
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete("/{file_name}/delete/", tags=["Import / Export"], status_code=200)
|
||||||
"/api/backups/{backup_name}/delete/",
|
def delete_backup(file_name: str):
|
||||||
tags=["Import / Export"],
|
|
||||||
status_code=200,
|
|
||||||
)
|
|
||||||
def delete_backup(backup_name: str):
|
|
||||||
""" Removes a database backup from the file system """
|
""" Removes a database backup from the file system """
|
||||||
|
|
||||||
try:
|
try:
|
||||||
BACKUP_DIR.joinpath(backup_name).unlink()
|
BACKUP_DIR.joinpath(file_name).unlink()
|
||||||
except:
|
except:
|
||||||
HTTPException(
|
HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail=SnackResponse.error("Unable to Delete Backup. See Log File"),
|
detail=SnackResponse.error("Unable to Delete Backup. See Log File"),
|
||||||
)
|
)
|
||||||
|
|
||||||
return SnackResponse.success(f"{backup_name} Deleted")
|
return SnackResponse.success(f"{file_name} Deleted")
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
from typing import List
|
|
||||||
|
|
||||||
from models.category_models import Category
|
|
||||||
from services.category_services import get_all
|
|
||||||
from fastapi import APIRouter, HTTPException
|
|
||||||
from utils.snackbar import SnackResponse
|
|
||||||
|
|
||||||
router = APIRouter(tags=["Category"])
|
|
||||||
|
|
||||||
@router.get("/api/category/all", response_model=List[Category])
|
|
||||||
def get_all_categories():
|
|
||||||
""" Returns a list of all categories """
|
|
||||||
|
|
||||||
return get_all()
|
|
||||||
@@ -6,17 +6,17 @@ from services.meal_services import MealPlan
|
|||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
from utils.snackbar import SnackResponse
|
from utils.snackbar import SnackResponse
|
||||||
|
|
||||||
router = APIRouter(tags=["Meal Plan"])
|
router = APIRouter(prefix="/api/meal-plan", tags=["Meal Plan"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/meal-plan/all/", response_model=List[MealPlan])
|
@router.get("/all/", response_model=List[MealPlan])
|
||||||
def get_all_meals(db: Session = Depends(generate_session)):
|
def get_all_meals(db: Session = Depends(generate_session)):
|
||||||
""" Returns a list of all available Meal Plan """
|
""" Returns a list of all available Meal Plan """
|
||||||
|
|
||||||
return MealPlan.get_all(db)
|
return MealPlan.get_all(db)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/meal-plan/create/")
|
@router.post("/create/")
|
||||||
def set_meal_plan(data: MealPlan, db: Session = Depends(generate_session)):
|
def set_meal_plan(data: MealPlan, db: Session = Depends(generate_session)):
|
||||||
""" Creates a meal plan database entry """
|
""" Creates a meal plan database entry """
|
||||||
data.process_meals(db)
|
data.process_meals(db)
|
||||||
@@ -30,7 +30,7 @@ def set_meal_plan(data: MealPlan, db: Session = Depends(generate_session)):
|
|||||||
return SnackResponse.success("Mealplan Created")
|
return SnackResponse.success("Mealplan Created")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/meal-plan/{plan_id}/update/")
|
@router.post("/{plan_id}/update/")
|
||||||
def update_meal_plan(
|
def update_meal_plan(
|
||||||
plan_id: str, meal_plan: MealPlan, db: Session = Depends(generate_session)
|
plan_id: str, meal_plan: MealPlan, db: Session = Depends(generate_session)
|
||||||
):
|
):
|
||||||
@@ -49,7 +49,7 @@ def update_meal_plan(
|
|||||||
return SnackResponse.success("Mealplan Updated")
|
return SnackResponse.success("Mealplan Updated")
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/api/meal-plan/{plan_id}/delete/")
|
@router.delete("/{plan_id}/delete/")
|
||||||
def delete_meal_plan(plan_id, db: Session = Depends(generate_session)):
|
def delete_meal_plan(plan_id, db: Session = Depends(generate_session)):
|
||||||
""" Removes a meal plan from the database """
|
""" Removes a meal plan from the database """
|
||||||
|
|
||||||
@@ -58,10 +58,7 @@ def delete_meal_plan(plan_id, db: Session = Depends(generate_session)):
|
|||||||
return SnackResponse.success("Mealplan Deleted")
|
return SnackResponse.success("Mealplan Deleted")
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get("/today/", tags=["Meal Plan"])
|
||||||
"/api/meal-plan/today/",
|
|
||||||
tags=["Meal Plan"],
|
|
||||||
)
|
|
||||||
def get_today(db: Session = Depends(generate_session)):
|
def get_today(db: Session = Depends(generate_session)):
|
||||||
"""
|
"""
|
||||||
Returns the recipe slug for the meal scheduled for today.
|
Returns the recipe slug for the meal scheduled for today.
|
||||||
@@ -71,7 +68,7 @@ def get_today(db: Session = Depends(generate_session)):
|
|||||||
return MealPlan.today(db)
|
return MealPlan.today(db)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/meal-plan/this-week/", response_model=MealPlan)
|
@router.get("/this-week/", response_model=MealPlan)
|
||||||
def get_this_week(db: Session = Depends(generate_session)):
|
def get_this_week(db: Session = Depends(generate_session)):
|
||||||
""" Returns the meal plan data for this week """
|
""" Returns the meal plan data for this week """
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ from services.migrations.nextcloud import migrate as nextcloud_migrate
|
|||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
from utils.snackbar import SnackResponse
|
from utils.snackbar import SnackResponse
|
||||||
|
|
||||||
router = APIRouter(tags=["Migration"])
|
router = APIRouter(prefix="/api/migrations", tags=["Migration"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/migrations/", response_model=List[Migrations])
|
@router.get("/", response_model=List[Migrations])
|
||||||
def get_avaiable_nextcloud_imports():
|
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 = []
|
||||||
@@ -35,7 +35,7 @@ def get_avaiable_nextcloud_imports():
|
|||||||
return response_data
|
return response_data
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/migrations/{type}/{file_name}/import/")
|
@router.post("/{type}/{file_name}/import/")
|
||||||
def import_nextcloud_directory(
|
def import_nextcloud_directory(
|
||||||
type: str, file_name: str, db: Session = Depends(generate_session)
|
type: str, file_name: str, db: Session = Depends(generate_session)
|
||||||
):
|
):
|
||||||
@@ -49,11 +49,11 @@ def import_nextcloud_directory(
|
|||||||
return SnackResponse.error("Incorrect Migration Type Selected")
|
return SnackResponse.error("Incorrect Migration Type Selected")
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/api/migrations/{folder}/{file}/delete/")
|
@router.delete("/{type}/{file_name}/delete/")
|
||||||
def delete_migration_data(folder: str, file: 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(folder, file)
|
remove_path = MIGRATION_DIR.joinpath(type, file_name)
|
||||||
|
|
||||||
if remove_path.is_file():
|
if remove_path.is_file():
|
||||||
remove_path.unlink()
|
remove_path.unlink()
|
||||||
@@ -65,7 +65,7 @@ def delete_migration_data(folder: str, file: str):
|
|||||||
return SnackResponse.info(f"Migration Data Remove: {remove_path.absolute()}")
|
return SnackResponse.info(f"Migration Data Remove: {remove_path.absolute()}")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/migrations/{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 = MIGRATION_DIR.joinpath(type)
|
||||||
|
|||||||
@@ -5,24 +5,23 @@ from sqlalchemy.orm.session import Session
|
|||||||
from utils.post_webhooks import post_webhooks
|
from utils.post_webhooks import post_webhooks
|
||||||
from utils.snackbar import SnackResponse
|
from utils.snackbar import SnackResponse
|
||||||
|
|
||||||
router = APIRouter(tags=["Settings"])
|
router = APIRouter(prefix="/api/site-settings", tags=["Settings"])
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
@router.get("/api/site-settings/")
|
|
||||||
def get_main_settings(db: Session = Depends(generate_session)):
|
def get_main_settings(db: Session = Depends(generate_session)):
|
||||||
""" Returns basic site settings """
|
""" Returns basic site settings """
|
||||||
|
|
||||||
return SiteSettings.get_site_settings(db)
|
return SiteSettings.get_site_settings(db)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/site-settings/webhooks/test/")
|
@router.post("/webhooks/test/")
|
||||||
def test_webhooks():
|
def test_webhooks():
|
||||||
""" Run the function to test your webhooks """
|
""" Run the function to test your webhooks """
|
||||||
|
|
||||||
return post_webhooks()
|
return post_webhooks()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/site-settings/update/")
|
@router.post("/update/")
|
||||||
def update_settings(data: SiteSettings, db: Session = Depends(generate_session)):
|
def update_settings(data: SiteSettings, db: Session = Depends(generate_session)):
|
||||||
""" Returns Site Settings """
|
""" Returns Site Settings """
|
||||||
data.update(db)
|
data.update(db)
|
||||||
@@ -36,20 +35,20 @@ def update_settings(data: SiteSettings, db: Session = Depends(generate_session))
|
|||||||
return SnackResponse.success("Settings Updated")
|
return SnackResponse.success("Settings Updated")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/site-settings/themes/", tags=["Themes"])
|
@router.get("/themes/", tags=["Themes"])
|
||||||
def get_all_themes(db: Session = Depends(generate_session)):
|
def get_all_themes(db: Session = Depends(generate_session)):
|
||||||
""" Returns all site themes """
|
""" Returns all site themes """
|
||||||
|
|
||||||
return SiteTheme.get_all(db)
|
return SiteTheme.get_all(db)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/site-settings/themes/{theme_name}/", tags=["Themes"])
|
@router.get("/themes/{theme_name}/", tags=["Themes"])
|
||||||
def get_single_theme(theme_name: str, db: Session = Depends(generate_session)):
|
def get_single_theme(theme_name: str, db: Session = Depends(generate_session)):
|
||||||
""" Returns a named theme """
|
""" Returns a named theme """
|
||||||
return SiteTheme.get_by_name(db, theme_name)
|
return SiteTheme.get_by_name(db, theme_name)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/site-settings/themes/create/", tags=["Themes"])
|
@router.post("/themes/create/", tags=["Themes"])
|
||||||
def create_theme(data: SiteTheme, db: Session = Depends(generate_session)):
|
def create_theme(data: SiteTheme, db: Session = Depends(generate_session)):
|
||||||
""" Creates a site color theme database entry """
|
""" Creates a site color theme database entry """
|
||||||
data.save_to_db(db)
|
data.save_to_db(db)
|
||||||
@@ -63,7 +62,7 @@ def create_theme(data: SiteTheme, db: Session = Depends(generate_session)):
|
|||||||
return SnackResponse.success("Theme Saved")
|
return SnackResponse.success("Theme Saved")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/site-settings/themes/{theme_name}/update/", tags=["Themes"])
|
@router.post("/themes/{theme_name}/update/", tags=["Themes"])
|
||||||
def update_theme(
|
def update_theme(
|
||||||
theme_name: str, data: SiteTheme, db: Session = Depends(generate_session)
|
theme_name: str, data: SiteTheme, db: Session = Depends(generate_session)
|
||||||
):
|
):
|
||||||
@@ -79,7 +78,7 @@ def update_theme(
|
|||||||
return SnackResponse.success("Theme Updated")
|
return SnackResponse.success("Theme Updated")
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/api/site-settings/themes/{theme_name}/delete/", tags=["Themes"])
|
@router.delete("/themes/{theme_name}/delete/", tags=["Themes"])
|
||||||
def delete_theme(theme_name: str, db: Session = Depends(generate_session)):
|
def delete_theme(theme_name: str, db: Session = Depends(generate_session)):
|
||||||
""" Deletes theme from the database """
|
""" Deletes theme from the database """
|
||||||
SiteTheme.delete_theme(db, theme_name)
|
SiteTheme.delete_theme(db, theme_name)
|
||||||
|
|||||||
5
mealie/run.sh
Normal file
5
mealie/run.sh
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
## Run Migration
|
||||||
|
|
||||||
|
|
||||||
|
## Start Application
|
||||||
|
uvicorn app:app --host 0.0.0.0 --port 80
|
||||||
Reference in New Issue
Block a user