site settings refactor

This commit is contained in:
hay-kot 2021-03-07 17:28:28 -09:00
commit e5bd261a3c
16 changed files with 149 additions and 156 deletions

View file

@ -1,5 +1,6 @@
from schema.meal import MealPlanInDB from schema.meal import MealPlanInDB
from schema.recipe import Recipe from schema.recipe import Recipe
from schema.settings import SiteSettings as SiteSettingsSchema
from schema.sign_up import SignUpOut from schema.sign_up import SignUpOut
from schema.user import GroupInDB, UserInDB from schema.user import GroupInDB, UserInDB
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
@ -8,7 +9,7 @@ from db.db_base import BaseDocument
from db.models.group import Group from db.models.group import Group
from db.models.mealplan import MealPlanModel from db.models.mealplan import MealPlanModel
from db.models.recipe.recipe import Category, RecipeModel, Tag from db.models.recipe.recipe import Category, RecipeModel, Tag
from db.models.settings import SiteSettingsModel from db.models.settings import SiteSettings
from db.models.sign_up import SignUp from db.models.sign_up import SignUp
from db.models.theme import SiteThemeModel from db.models.theme import SiteThemeModel
from db.models.users import User from db.models.users import User
@ -53,9 +54,10 @@ class _Meals(BaseDocument):
class _Settings(BaseDocument): class _Settings(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "name" self.primary_key = "id"
self.sql_model = SiteSettingsModel self.sql_model = SiteSettings
self.orm_mode = False self.orm_mode = True
self.schema = SiteSettingsSchema
class _Themes(BaseDocument): class _Themes(BaseDocument):

View file

@ -1,7 +1,7 @@
from core.config import DEFAULT_GROUP from core.config import DEFAULT_GROUP
from core.security import get_password_hash from core.security import get_password_hash
from fastapi.logger import logger from fastapi.logger import logger
from schema.settings import SiteSettings, Webhooks from schema.settings import SiteSettings
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
@ -43,13 +43,9 @@ def default_theme_init(session: Session):
def default_settings_init(session: Session): def default_settings_init(session: Session):
try: data = {"language": "en", "sidebar": {"categories": []}}
webhooks = Webhooks() document = db.settings.create(session, data)
default_entry = SiteSettings(name="main", webhooks=webhooks) logger.info(f"Created Site Settings: \n {document}")
document = db.settings.create(session, default_entry.dict())
logger.info(f"Created Site Settings: \n {document}")
except:
pass
def default_group_init(session: Session): def default_group_init(session: Session):

View file

@ -2,8 +2,8 @@ import sqlalchemy as sa
import sqlalchemy.orm as orm import sqlalchemy.orm as orm
from core.config import DEFAULT_GROUP from core.config import DEFAULT_GROUP
from db.models.model_base import BaseMixins, SqlAlchemyBase from db.models.model_base import BaseMixins, SqlAlchemyBase
from db.models.recipe.category import group2categories
from fastapi.logger import logger from fastapi.logger import logger
from slugify import slugify
class Group(SqlAlchemyBase, BaseMixins): class Group(SqlAlchemyBase, BaseMixins):
@ -12,6 +12,8 @@ class Group(SqlAlchemyBase, BaseMixins):
name = sa.Column(sa.String, index=True, nullable=False, unique=True) name = sa.Column(sa.String, index=True, nullable=False, unique=True)
users = orm.relationship("User", back_populates="group") users = orm.relationship("User", back_populates="group")
mealplans = orm.relationship("MealPlanModel", back_populates="group") mealplans = orm.relationship("MealPlanModel", back_populates="group")
categories = orm.relationship("Category", secondary=group2categories)
webhooks = orm.relationship("WebHookModel", uselist=False, cascade="all, delete")
def __init__(self, name, session=None) -> None: def __init__(self, name, session=None) -> None:
self.name = name self.name = name
@ -29,3 +31,37 @@ class Group(SqlAlchemyBase, BaseMixins):
except: except:
logger.info("Category doesn't exists, creating category") logger.info("Category doesn't exists, creating category")
return Group(name=name) return Group(name=name)
class WebHookModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "webhook_settings"
id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("groups.id"))
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, session=None
) -> 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)
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"))

View file

@ -8,9 +8,8 @@ SqlAlchemyBase = dec.declarative_base()
class BaseMixins: class BaseMixins:
@staticmethod @staticmethod
def _sql_remove_list(session, list_of_tables: list, parent_id): def _sql_remove_list(session, list_of_tables: list, parent_id):
for table in list_of_tables: for table in list_of_tables:
session.query(table).filter_by(parent_id=parent_id).delete() session.query(table).filter(parent_id == parent_id).delete()
@staticmethod @staticmethod
def _flatten_dict(list_of_dict: List[dict]): def _flatten_dict(list_of_dict: List[dict]):

View file

@ -5,6 +5,20 @@ from fastapi.logger import logger
from slugify import slugify from slugify import slugify
from sqlalchemy.orm import validates from sqlalchemy.orm import validates
sidebar2categories = sa.Table(
"sidebar2categories",
SqlAlchemyBase.metadata,
sa.Column("sidebar_id", sa.Integer, sa.ForeignKey("site_sidebar.id")),
sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")),
)
group2categories = sa.Table(
"group2categories",
SqlAlchemyBase.metadata,
sa.Column("group_id", sa.Integer, sa.ForeignKey("groups.id")),
sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")),
)
recipes2categories = sa.Table( recipes2categories = sa.Table(
"recipes2categories", "recipes2categories",
SqlAlchemyBase.metadata, SqlAlchemyBase.metadata,
@ -45,21 +59,3 @@ class Category(SqlAlchemyBase):
except: except:
logger.info("Category doesn't exists, creating category") logger.info("Category doesn't exists, creating category")
return Category(name=name) return Category(name=name)
def to_str(self):
return self.name
def dict(self):
return {
"id": self.id,
"slug": self.slug,
"name": self.name,
"recipes": [x.dict() for x in self.recipes],
}
def dict_no_recipes(self):
return {
"id": self.id,
"slug": self.slug,
"name": self.name,
}

View file

@ -1,93 +1,42 @@
import sqlalchemy as sa import sqlalchemy as sa
import sqlalchemy.orm as orm import sqlalchemy.orm as orm
from db.models.model_base import BaseMixins, SqlAlchemyBase from db.models.model_base import BaseMixins, SqlAlchemyBase
from db.models.recipe.category import Category, sidebar2categories
from sqlalchemy.orm import Session
class SiteSettingsModel(SqlAlchemyBase, BaseMixins): class Sidebar(SqlAlchemyBase, BaseMixins):
__tablename__ = "site_sidebar"
id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.Integer, sa.ForeignKey("site_settings.id"))
categories = orm.relationship("Category", secondary=sidebar2categories, cascade="delete")
def __init__(self, session: Session, sidebar: dict) -> None:
categories = sidebar.get("categories")
new_categories = []
if not categories:
return None
for cat in categories:
slug = cat.get("slug")
cat_in_db = session.query(Category).filter(Category.slug == slug).one()
new_categories.append(cat_in_db)
self.categories = new_categories
class SiteSettings(SqlAlchemyBase, BaseMixins):
__tablename__ = "site_settings" __tablename__ = "site_settings"
name = sa.Column(sa.String, primary_key=True) id = sa.Column(sa.Integer, primary_key=True)
planCategories = orm.relationship( language = sa.Column(sa.String)
"MealCategory", uselist=True, cascade="all, delete" sidebar = orm.relationship("Sidebar", uselist=False, cascade="all")
)
webhooks = orm.relationship("WebHookModel", uselist=False, cascade="all, delete")
def __init__( def __init__(
self, name: str = None, webhooks: dict = None, planCategories=[], session=None self, session=None, language="en", sidebar: list = {"categories": []}
) -> None: ) -> None:
self.name = name self._sql_remove_list(session, [Sidebar], self.id)
self.planCategories = [MealCategory(cat) for cat in planCategories]
self.webhooks = WebHookModel(**webhooks)
def update(self, session, name, webhooks: dict, planCategories=[]) -> dict: self.language = language
self.sidebar = Sidebar(session, sidebar)
self._sql_remove_list(session, [MealCategory], self.name) def update(self, session, language, sidebar):
self.name = name self.__init__(session=session, language=language, sidebar=sidebar)
self.planCategories = [MealCategory(x) for x in planCategories]
self.webhooks.update(session=session, **webhooks)
return
def dict(self):
data = {
"name": self.name,
"planCategories": [cat.to_str() for cat in self.planCategories],
"webhooks": self.webhooks.dict(),
}
return data
class MealCategory(SqlAlchemyBase):
__tablename__ = "meal_plan_categories"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String)
parent_id = sa.Column(sa.Integer, sa.ForeignKey("site_settings.name"))
def __init__(self, name) -> None:
self.name = name
def to_str(self):
return self.name
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, session=None
) -> 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

@ -1,6 +1,6 @@
import sqlalchemy as sa import sqlalchemy as sa
import sqlalchemy.orm as orm import sqlalchemy.orm as orm
from db.models.model_base import BaseMixins, SqlAlchemyBase from db.models.model_base import SqlAlchemyBase
class SiteThemeModel(SqlAlchemyBase): class SiteThemeModel(SqlAlchemyBase):

View file

@ -3,6 +3,12 @@ from db.models.group import Group
from db.models.model_base import BaseMixins, SqlAlchemyBase from 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
# I'm not sure this is necessasry, browser based settings may be sufficient
# class UserSettings(SqlAlchemyBase, BaseMixins):
# __tablename__ = "user_settings"
# id = Column(Integer, primary_key=True, index=True)
# parent_id = Column(String, ForeignKey("users.id"))
class User(SqlAlchemyBase, BaseMixins): class User(SqlAlchemyBase, BaseMixins):
__tablename__ = "users" __tablename__ = "users"

View file

@ -5,6 +5,7 @@ from db.database import db
from db.db_setup import generate_session from db.db_setup import generate_session
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from schema.meal import MealPlanBase, MealPlanInDB from schema.meal import MealPlanBase, MealPlanInDB
from schema.recipe import Recipe
from schema.snackbar import SnackResponse from schema.snackbar import SnackResponse
from services.meal_services import get_todays_meal, process_meals from services.meal_services import get_todays_meal, process_meals
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
@ -26,9 +27,9 @@ def get_shopping_list(id: str, session: Session = Depends(generate_session)):
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]
recipes = [db.recipes.get(session, x) for x in slugs] recipes: list[Recipe] = [db.recipes.get(session, x) for x in slugs]
ingredients = [ ingredients = [
{"name": x.get("name"), "recipeIngredient": x.get("recipeIngredient")} {"name": x.name, "recipeIngredient": x.recipeIngredient}
for x in recipes for x in recipes
if x if x
] ]

View file

@ -2,9 +2,9 @@ from db.database import db
from db.db_setup import generate_session from db.db_setup import generate_session
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from schema.settings import SiteSettings from schema.settings import SiteSettings
from schema.snackbar import SnackResponse
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
from utils.post_webhooks import post_webhooks from utils.post_webhooks import post_webhooks
from schema.snackbar import SnackResponse
router = APIRouter(prefix="/api/site-settings", tags=["Settings"]) router = APIRouter(prefix="/api/site-settings", tags=["Settings"])
@ -13,17 +13,15 @@ router = APIRouter(prefix="/api/site-settings", tags=["Settings"])
def get_main_settings(session: Session = Depends(generate_session)): def get_main_settings(session: Session = Depends(generate_session)):
""" Returns basic site settings """ """ Returns basic site settings """
try: data = db.settings.get(session, 1)
data = db.settings.get(session, "main")
except:
return
return data return data
@router.put("") @router.put("")
def update_settings(data: SiteSettings, session: Session = Depends(generate_session)): def update_settings(data: SiteSettings, session: Session = Depends(generate_session)):
""" Returns Site Settings """ """ Returns Site Settings """
db.settings.update(session, "main", data.dict()) db.settings.update(session, 1, data.dict())
return SnackResponse.success("Settings Updated") return SnackResponse.success("Settings Updated")

View file

@ -82,7 +82,6 @@ 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 = USER_DIR.joinpath(id)
for recipe_image in user_dir.glob("profile_image.*"): for recipe_image in user_dir.glob("profile_image.*"):
print(recipe_image)
return FileResponse(recipe_image) return FileResponse(recipe_image)
else: else:
return False return False
@ -128,7 +127,6 @@ async def update_password(
match_passwords = verify_password( match_passwords = verify_password(
password_change.current_password, current_user.password password_change.current_password, current_user.password
) )
print(match_passwords)
match_id = current_user.id == id match_id = current_user.id == id
if match_passwords and match_id: if match_passwords and match_id:

View file

@ -3,6 +3,8 @@
## Migrations ## Migrations
# TODO # TODO
# Database Init
## Web Server ## Web Server
caddy start --config ./Caddyfile caddy start --config ./Caddyfile

View file

@ -1,13 +1,20 @@
from typing import List, Optional from typing import List, Optional
from pydantic.main import BaseModel from fastapi_camelcase import CamelModel
from schema.recipe import Recipe from schema.recipe import Recipe
class RecipeCategoryResponse(BaseModel): class CategoryBase(CamelModel):
id: int id: int
name: str name: str
slug: str slug: str
class Config:
orm_mode = True
class RecipeCategoryResponse(CategoryBase):
recipes: Optional[List[Recipe]] recipes: Optional[List[Recipe]]
class Config: class Config:

View file

@ -1,9 +1,8 @@
import datetime import datetime
from typing import Any, List, Optional from typing import Any, List, Optional
from db.models.recipe.ingredient import RecipeIngredient
from db.models.recipe.recipe import RecipeModel from db.models.recipe.recipe import RecipeModel
from pydantic import BaseModel, Schema, validator from pydantic import BaseModel, validator
from pydantic.utils import GetterDict from pydantic.utils import GetterDict
from slugify import slugify from slugify import slugify
@ -36,7 +35,6 @@ class Nutrition(BaseModel):
class Recipe(BaseModel): class Recipe(BaseModel):
# Standard Schema
name: str name: str
description: Optional[str] description: Optional[str]
image: Optional[Any] image: Optional[Any]
@ -63,13 +61,13 @@ class Recipe(BaseModel):
orm_mode = True orm_mode = True
@classmethod @classmethod
def getter_dict(cls, name_orm: RecipeModel): def getter_dict(_cls, name_orm: RecipeModel):
print(name_orm.recipeIngredient)
return { return {
**GetterDict(name_orm), **GetterDict(name_orm),
"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],
"extras": {x.key_name: x.value for x in name_orm.extras},
} }
schema_extra = { schema_extra = {

View file

@ -1,28 +1,24 @@
from typing import List, Optional from typing import Optional
from pydantic import BaseModel from fastapi_camelcase import CamelModel
from schema.category import CategoryBase
class Webhooks(BaseModel): class Sidebar(CamelModel):
webhookTime: str = "00:00" categories: Optional[list[CategoryBase]]
webhookURLs: Optional[List[str]] = []
enabled: bool = False
class SiteSettings(BaseModel):
name: str = "main"
planCategories: list[str] = []
webhooks: Webhooks
class Config: class Config:
orm_mode = True
class SiteSettings(CamelModel):
language: str
sidebar: Sidebar
class Config:
orm_mode = True
schema_extra = { schema_extra = {
"example": { "example": {"id": "1", "language": "en", "sidebar": ["// TODO"]}
"name": "main",
"planCategories": ["dinner", "lunch"],
"webhooks": {
"webhookTime": "00:00",
"webhookURLs": ["https://mywebhookurl.com/webhook"],
"enable": False,
},
}
} }

View file

@ -1,7 +1,9 @@
from typing import Optional from typing import Optional
from core.config import DEFAULT_GROUP from core.config import DEFAULT_GROUP
from db.models.users import User
from fastapi_camelcase import CamelModel from fastapi_camelcase import CamelModel
from pydantic.utils import GetterDict
class ChangePassword(CamelModel): class ChangePassword(CamelModel):
@ -40,11 +42,18 @@ class UserIn(UserBase):
class UserOut(UserBase): class UserOut(UserBase):
id: int id: int
group: GroupBase group: str
class Config: class Config:
orm_mode = True orm_mode = True
@classmethod
def getter_dict(cls, ormModel: User):
return {
**GetterDict(ormModel),
"group": ormModel.group.name,
}
class UserInDB(UserOut): class UserInDB(UserOut):
password: str password: str