diff --git a/frontend/src/api/groups.js b/frontend/src/api/groups.js
new file mode 100644
index 000000000..8cef18655
--- /dev/null
+++ b/frontend/src/api/groups.js
@@ -0,0 +1,14 @@
+import { baseURL } from "./api-utils";
+import { apiReq } from "./api-utils";
+const groupPrefix = baseURL + "groups";
+
+const groupsURLs = {
+ groups: `${groupPrefix}`,
+};
+
+export default {
+ async allGroups() {
+ let response = await apiReq.get(groupsURLs.groups);
+ return response.data;
+ },
+};
diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js
index 0caf3b76d..9910ac96b 100644
--- a/frontend/src/api/index.js
+++ b/frontend/src/api/index.js
@@ -9,6 +9,7 @@ import category from "./category";
import meta from "./meta";
import users from "./users";
import signUps from "./signUps";
+import groups from "./groups";
export default {
recipes: recipe,
@@ -22,4 +23,5 @@ export default {
meta: meta,
users: users,
signUps: signUps,
+ groups: groups,
};
diff --git a/frontend/src/components/Admin/ManageUsers/TheGroupTable.vue b/frontend/src/components/Admin/ManageUsers/TheGroupTable.vue
index 50c625190..ad8a96854 100644
--- a/frontend/src/components/Admin/ManageUsers/TheGroupTable.vue
+++ b/frontend/src/components/Admin/ManageUsers/TheGroupTable.vue
@@ -19,6 +19,10 @@
User Groups
+
+
+
+
@@ -136,7 +140,7 @@ export default {
activeName: null,
headers: [
{
- text: "User ID",
+ text: "User IDs",
align: "start",
sortable: false,
value: "id",
diff --git a/frontend/src/pages/Admin/ManageUsers/index.vue b/frontend/src/pages/Admin/ManageUsers/index.vue
index 3f4c4b8aa..e70bd3810 100644
--- a/frontend/src/pages/Admin/ManageUsers/index.vue
+++ b/frontend/src/pages/Admin/ManageUsers/index.vue
@@ -26,7 +26,7 @@
-
+
diff --git a/mealie/db/database.py b/mealie/db/database.py
index 0e6a11fab..1c6e3a5dc 100644
--- a/mealie/db/database.py
+++ b/mealie/db/database.py
@@ -1,7 +1,9 @@
+from schema.category import RecipeCategoryResponse
from schema.meal import MealPlanInDB
from schema.recipe import Recipe
from schema.settings import SiteSettings as SiteSettingsSchema
from schema.sign_up import SignUpOut
+from schema.theme import SiteTheme
from schema.user import GroupInDB, UserInDB
from sqlalchemy.orm.session import Session
@@ -34,7 +36,8 @@ class _Categories(BaseDocument):
def __init__(self) -> None:
self.primary_key = "slug"
self.sql_model = Category
- self.orm_mode = False
+ self.orm_mode = True
+ self.schema = RecipeCategoryResponse
class _Tags(BaseDocument):
@@ -64,7 +67,8 @@ class _Themes(BaseDocument):
def __init__(self) -> None:
self.primary_key = "name"
self.sql_model = SiteThemeModel
- self.orm_mode = False
+ self.orm_mode = True
+ self.schema = SiteTheme
class _Users(BaseDocument):
diff --git a/mealie/db/init_db.py b/mealie/db/init_db.py
index f83cc8c72..915c3dba5 100644
--- a/mealie/db/init_db.py
+++ b/mealie/db/init_db.py
@@ -2,6 +2,7 @@ from core.config import DEFAULT_GROUP
from core.security import get_password_hash
from fastapi.logger import logger
from schema.settings import SiteSettings
+from schema.theme import SiteTheme
from sqlalchemy.orm import Session
from sqlalchemy.orm.session import Session
@@ -22,29 +23,17 @@ def init_db(db: Session = None) -> None:
def default_theme_init(session: Session):
- default_theme = {
- "name": "default",
- "colors": {
- "primary": "#E58325",
- "accent": "#00457A",
- "secondary": "#973542",
- "success": "#5AB1BB",
- "info": "#4990BA",
- "warning": "#FF4081",
- "error": "#EF5350",
- },
- }
+ db.themes.create(session, SiteTheme().dict())
try:
- db.themes.create(session, default_theme)
logger.info("Generating default theme...")
except:
logger.info("Default Theme Exists.. skipping generation")
def default_settings_init(session: Session):
- data = {"language": "en", "sidebar": {"categories": []}}
- document = db.settings.create(session, data)
+ data = {"language": "en", "home_page_settings": {"categories": []}}
+ document = db.settings.create(session, SiteSettings().dict())
logger.info(f"Created Site Settings: \n {document}")
diff --git a/mealie/db/models/model_base.py b/mealie/db/models/model_base.py
index 9a77a5d76..f62ec6cdf 100644
--- a/mealie/db/models/model_base.py
+++ b/mealie/db/models/model_base.py
@@ -1,13 +1,14 @@
from typing import List
import sqlalchemy.ext.declarative as dec
+from sqlalchemy.orm.session import Session
SqlAlchemyBase = dec.declarative_base()
class BaseMixins:
@staticmethod
- def _sql_remove_list(session, list_of_tables: list, parent_id):
+ def _sql_remove_list(session: Session, list_of_tables: list, parent_id):
for table in list_of_tables:
session.query(table).filter(parent_id == parent_id).delete()
@@ -19,3 +20,29 @@ class BaseMixins:
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)
+ print("Complex", complex_attributed)
+ func(class_object, complex_attributed)
+
+ return wrapper
diff --git a/mealie/db/models/recipe/category.py b/mealie/db/models/recipe/category.py
index cdccbea26..46a471bb3 100644
--- a/mealie/db/models/recipe/category.py
+++ b/mealie/db/models/recipe/category.py
@@ -5,10 +5,10 @@ from fastapi.logger import logger
from slugify import slugify
from sqlalchemy.orm import validates
-sidebar2categories = sa.Table(
- "sidebar2categories",
+site_settings2categories = sa.Table(
+ "site_settings2categoories",
SqlAlchemyBase.metadata,
- sa.Column("sidebar_id", sa.Integer, sa.ForeignKey("site_sidebar.id")),
+ sa.Column("sidebar_id", sa.Integer, sa.ForeignKey("site_settings.id")),
sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")),
)
diff --git a/mealie/db/models/recipe/recipe.py b/mealie/db/models/recipe/recipe.py
index 6a74238a4..b36453118 100644
--- a/mealie/db/models/recipe/recipe.py
+++ b/mealie/db/models/recipe/recipe.py
@@ -182,30 +182,3 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
orgURL=orgURL,
extras=extras,
)
-
- # def dict(self):
- # data = {
- # "name": self.name,
- # "description": self.description,
- # "image": self.image,
- # "recipeYield": self.recipeYield,
- # "recipeCuisine": self.recipeCuisine,
- # "recipeCategory": [x.to_str() for x in self.recipeCategory],
- # "recipeIngredient": [x.to_str() for x in self.recipeIngredient],
- # "recipeInstructions": [x.dict() for x in self.recipeInstructions],
- # "nutrition": self.nutrition.dict(),
- # "totalTime": self.totalTime,
- # "prepTime": self.prepTime,
- # "performTime": self.performTime,
- # "tool": [x.str() for x in self.tool],
- # # Mealie Specific
- # "slug": self.slug,
- # "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
diff --git a/mealie/db/models/settings.py b/mealie/db/models/settings.py
index ca9b89592..8986c3ed9 100644
--- a/mealie/db/models/settings.py
+++ b/mealie/db/models/settings.py
@@ -1,42 +1,32 @@
import sqlalchemy as sa
import sqlalchemy.orm as orm
from db.models.model_base import BaseMixins, SqlAlchemyBase
-from db.models.recipe.category import Category, sidebar2categories
+from db.models.recipe.category import Category, site_settings2categories
from sqlalchemy.orm import Session
-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"
id = sa.Column(sa.Integer, primary_key=True)
language = sa.Column(sa.String)
- sidebar = orm.relationship("Sidebar", uselist=False, cascade="all")
+ categories = orm.relationship(
+ "Category",
+ secondary=site_settings2categories,
+ single_parent=True,
+ )
+ show_recent = sa.Column(sa.Boolean, default=True)
def __init__(
- self, session=None, language="en", sidebar: list = {"categories": []}
+ self, session: Session = None, language="en", categories: list = [], show_recent=True
) -> None:
- self._sql_remove_list(session, [Sidebar], self.id)
-
+ session.commit()
self.language = language
- self.sidebar = Sidebar(session, sidebar)
- def update(self, session, language, sidebar):
- self.__init__(session=session, language=language, sidebar=sidebar)
+ self.show_recent = show_recent
+ self.categories = [
+ Category.create_if_not_exist(session=session, name=cat.get("name"))
+ for cat in categories
+ ]
+
+ def update(self, *args, **kwarg):
+ self.__init__(*args, **kwarg)
diff --git a/mealie/routes/groups/crud.py b/mealie/routes/groups/crud.py
index 541fa0cf5..d00a06fa0 100644
--- a/mealie/routes/groups/crud.py
+++ b/mealie/routes/groups/crud.py
@@ -1,12 +1,6 @@
-import shutil
-from datetime import timedelta
-
-from core.config import USER_DIR
-from core.security import get_password_hash, verify_password
from db.database import db
from db.db_setup import generate_session
-from fastapi import APIRouter, Depends, File, UploadFile
-from fastapi.responses import FileResponse
+from fastapi import APIRouter, Depends
from routes.deps import manager
from schema.snackbar import SnackResponse
from schema.user import GroupBase, GroupInDB
@@ -21,4 +15,52 @@ async def get_all_groups(
session: Session = Depends(generate_session),
):
""" Returns a list of all groups in the database """
+
return db.groups.get_all(session)
+
+
+@router.post("")
+async def create_group(
+ group_data: GroupBase,
+ current_user=Depends(manager),
+ session: Session = Depends(generate_session),
+):
+ """ Creates a Group in the Database """
+
+ db.groups.create(session, group_data.dict())
+
+ return
+
+
+@router.put("/{id}")
+async def update_group_data(
+ id: int,
+ group_data: GroupInDB,
+ current_user=Depends(manager),
+ session: Session = Depends(generate_session),
+):
+ """ Updates a User Group """
+
+ return db.groups.update(session, id, group_data)
+
+
+@router.delete("/{id}")
+async def delete_user_group(
+ id: int, current_user=Depends(manager), session: Session = Depends(generate_session)
+):
+ """ Removes a user group from the database """
+
+ if id == 1:
+ return SnackResponse.error("Cannot delete default group")
+
+ group: GroupInDB = db.groups.get(session, id)
+
+ if not group:
+ return SnackResponse.error("Group not found")
+
+ if not group.users == []:
+ return SnackResponse.error("Cannot delete group with users")
+
+ db.groups.delete(session, id)
+
+ return
diff --git a/mealie/schema/meal.py b/mealie/schema/meal.py
index 0e90a832d..68d79cd18 100644
--- a/mealie/schema/meal.py
+++ b/mealie/schema/meal.py
@@ -29,9 +29,11 @@ class MealPlanBase(BaseModel):
raise ValueError("EndDate should be greater than StartDate")
return v
+
class MealPlanProcessed(MealPlanBase):
meals: list[MealOut]
+
class MealPlanInDB(MealPlanProcessed):
uid: str
@@ -39,7 +41,6 @@ class MealPlanInDB(MealPlanProcessed):
orm_mode = True
-
class MealPlan(BaseModel):
uid: Optional[str]
diff --git a/mealie/schema/settings.py b/mealie/schema/settings.py
index bfdd4cec4..6b525ac0b 100644
--- a/mealie/schema/settings.py
+++ b/mealie/schema/settings.py
@@ -5,20 +5,22 @@ from fastapi_camelcase import CamelModel
from schema.category import CategoryBase
-class Sidebar(CamelModel):
- categories: Optional[list[CategoryBase]]
-
- class Config:
- orm_mode = True
-
-
class SiteSettings(CamelModel):
- language: str
- sidebar: Sidebar
+ language: str = "en"
+ show_recent: bool = True
+ categories: Optional[list[CategoryBase]] = []
class Config:
orm_mode = True
schema_extra = {
- "example": {"id": "1", "language": "en", "sidebar": ["// TODO"]}
+ "example": {
+ "language": "en",
+ "showRecent": True,
+ "categories": [
+ {"id": 1, "name": "thanksgiving", "slug": "thanksgiving"},
+ {"id": 2, "name": "homechef", "slug": "homechef"},
+ {"id": 3, "name": "potatoes", "slug": "potatoes"},
+ ],
+ }
}
diff --git a/mealie/schema/theme.py b/mealie/schema/theme.py
index 6b65da6f2..b98594ac4 100644
--- a/mealie/schema/theme.py
+++ b/mealie/schema/theme.py
@@ -1,20 +1,25 @@
from pydantic import BaseModel
+
class Colors(BaseModel):
- primary: str
- accent: str
- secondary: str
- success: str
- info: str
- warning: str
- error: str
+ primary: str = "#E58325"
+ accent: str = "#00457A"
+ secondary: str = "#973542"
+ success: str = "#5AB1BB"
+ info: str = "#4990BA"
+ warning: str = "#FF4081"
+ error: str = "#EF5350"
+
+ class Config:
+ orm_mode = True
class SiteTheme(BaseModel):
- name: str
- colors: Colors
+ name: str = "default"
+ colors: Colors = Colors()
class Config:
+ orm_mode = True
schema_extra = {
"example": {
"name": "default",
@@ -28,4 +33,4 @@ class SiteTheme(BaseModel):
"error": "#EF5350",
},
}
- }
\ No newline at end of file
+ }
diff --git a/mealie/schema/user.py b/mealie/schema/user.py
index 885593508..516811ddd 100644
--- a/mealie/schema/user.py
+++ b/mealie/schema/user.py
@@ -1,10 +1,14 @@
-from typing import Optional
+from typing import Any, Optional
from core.config import DEFAULT_GROUP
+from db.models.group import WebHookModel
from db.models.users import User
from fastapi_camelcase import CamelModel
from pydantic.utils import GetterDict
+from schema.category import CategoryBase
+from schema.meal import MealPlanInDB
+
class ChangePassword(CamelModel):
current_password: str
@@ -27,7 +31,13 @@ class UserBase(CamelModel):
class Config:
orm_mode = True
- class Config:
+ @classmethod
+ def getter_dict(_cls, name_orm: User):
+ return {
+ **GetterDict(name_orm),
+ "group": name_orm.group.name,
+ }
+
schema_extra = {
"fullName": "Change Me",
"email": "changeme@email.com",
@@ -63,10 +73,29 @@ class UserInDB(UserOut):
orm_mode = True
+class Webhooks(CamelModel):
+ webhookURLs: list[str] = []
+ webhookTime: str = "00:00"
+ enabled: bool = False
+
+ class Config:
+ orm_mode = True
+
+ @classmethod
+ def getter_dict(_cls, orm_model: WebHookModel):
+ return {
+ **GetterDict(orm_model),
+ "webookURLs": [x.url for x in orm_model.webhookURLs],
+ }
+
+
class GroupInDB(GroupBase):
id: int
name: str
users: Optional[list[UserOut]]
+ mealplans: Optional[list[MealPlanInDB]]
+ categories: Optional[list[CategoryBase]]
+ webhooks: Optional[Webhooks]
class Config:
orm_mode = True
diff --git a/mealie/tests/test_routes/test_settings_routes.py b/mealie/tests/test_routes/test_settings_routes.py
index fe4bbdf65..d55392df5 100644
--- a/mealie/tests/test_routes/test_settings_routes.py
+++ b/mealie/tests/test_routes/test_settings_routes.py
@@ -1,4 +1,6 @@
import json
+from schema.settings import SiteSettings
+from schema.theme import SiteTheme
import pytest
from tests.utils.routes import (
@@ -11,30 +13,12 @@ from tests.utils.routes import (
@pytest.fixture(scope="function")
def default_settings():
- return {
- "name": "main",
- "planCategories": [],
- "webhooks": {"webhookTime": "00:00", "webhookURLs": [], "enabled": False},
- }
+ return SiteSettings().dict(by_alias=True)
@pytest.fixture(scope="session")
-def default_theme(api_client):
-
- default_theme = {
- "name": "default",
- "colors": {
- "primary": "#E58325",
- "accent": "#00457A",
- "secondary": "#973542",
- "success": "#5AB1BB",
- "info": "#4990BA",
- "warning": "#FF4081",
- "error": "#EF5350",
- },
- }
-
- return default_theme
+def default_theme():
+ return SiteTheme().dict()
@pytest.fixture(scope="session")
@@ -62,11 +46,8 @@ def test_default_settings(api_client, default_settings):
def test_update_settings(api_client, default_settings):
- default_settings["webhooks"]["webhookURLs"] = [
- "https://test1.url.com",
- "https://test2.url.com",
- "https://test3.url.com",
- ]
+ default_settings["language"] = "fr"
+ default_settings["showRecent"] = False
response = api_client.put(SETTINGS_UPDATE, json=default_settings)
diff --git a/mealie/tests/test_routes/test_user_routes.py b/mealie/tests/test_routes/test_user_routes.py
index e2c63b388..441f39dec 100644
--- a/mealie/tests/test_routes/test_user_routes.py
+++ b/mealie/tests/test_routes/test_user_routes.py
@@ -16,9 +16,7 @@ def default_user():
"id": 1,
"fullName": "Change Me",
"email": "changeme@email.com",
- "group": {
- "name": "home"
- },
+ "group": "home",
"admin": True
}
@@ -29,9 +27,7 @@ def new_user():
"id": 2,
"fullName": "My New User",
"email": "newuser@email.com",
- "group": {
- "name": "home"
- },
+ "group": "home",
"admin": False
}