database refactoring

This commit is contained in:
hay-kot 2021-03-06 18:08:56 -09:00
commit db61ac8a31
26 changed files with 234 additions and 113 deletions

View file

@ -1,10 +1,15 @@
# Release Notes # Release Notes
## v0.4.0 Whoa, What a Release! [DRAFT] ## v0.4.0 Whoa, What a Release! [DRAFT]
- Authentication! Tons of stuff went into creating a flexible authentication platform for a lot of different use cases. Review the documentation for more information on how to use the authentication, and how everything works together.
### Bug Fixes
### Features and Improvements
- Authentication! Tons of stuff went into creating a flexible authentication platform for a lot of different use cases. Review the documentation for more information on how to use the authentication, and how everything works together. Some key features include
- Sign Up Links - Sign Up Links
- Admin and User Roles - Admin and User Roles
- Group Management - Group Management
- Create/Edit/Delete Restrictions
- Recipe Database Refactoring. Tons of new information is now stored for recipes in the database. Not all is accessible via the UI, but it's coming. - Recipe Database Refactoring. Tons of new information is now stored for recipes in the database. Not all is accessible via the UI, but it's coming.
- Nutrition Information - Nutrition Information
- calories - calories
@ -13,9 +18,18 @@
- proteinContent - proteinContent
- sodiumContent - sodiumContent
- sugarContent - sugarContent
- recipeCuisine - recipeCuisine has been added
- "categories" has been migrated to "recipeCategory" to adhear closer to the standard schema - "categories" has been migrated to "recipeCategory" to adhear closer to the standard schema
- "tool" - a list of tools used for the recipe - "tool" - a list of tools used for the recipe
- Removed CDN dependencies
- Completed Redesign of the Admin Panel
- Profile Pages
- Side Panel Menu
- Language selector is now displayed on all pages and does not require an account
### Development / Misc
- Database Model Refactoring
- File/Folder Name Refactoring
## v0.3.0 ## v0.3.0

View file

@ -63,8 +63,8 @@
</v-col> </v-col>
<v-col cols="12" sm="12" md="6"> <v-col cols="12" sm="12" md="6">
<v-text-field <v-text-field
v-model="editedItem.family" v-model="editedItem.group"
label="Family Group" label="Group Group"
></v-text-field> ></v-text-field>
</v-col> </v-col>
<v-col cols="12" sm="12" md="6" v-if="showPassword"> <v-col cols="12" sm="12" md="6" v-if="showPassword">
@ -143,7 +143,7 @@ export default {
}, },
{ text: "Full Name", value: "fullName" }, { text: "Full Name", value: "fullName" },
{ text: "Email", value: "email" }, { text: "Email", value: "email" },
{ text: "Family", value: "family" }, { text: "Group", value: "group" },
{ text: "Admin", value: "admin" }, { text: "Admin", value: "admin" },
{ text: "", value: "actions", sortable: false, align: "center" }, { text: "", value: "actions", sortable: false, align: "center" },
], ],
@ -154,7 +154,7 @@ export default {
fullName: "", fullName: "",
password: "", password: "",
email: "", email: "",
family: "", group: "",
admin: false, admin: false,
}, },
defaultItem: { defaultItem: {
@ -162,7 +162,7 @@ export default {
fullName: "", fullName: "",
password: "", password: "",
email: "", email: "",
family: "", group: "",
admin: false, admin: false,
}, },
}), }),

View file

@ -63,8 +63,8 @@
</v-col> </v-col>
<v-col cols="12" sm="12" md="6"> <v-col cols="12" sm="12" md="6">
<v-text-field <v-text-field
v-model="editedItem.family" v-model="editedItem.group"
label="Family Group" label="Group Group"
></v-text-field> ></v-text-field>
</v-col> </v-col>
<v-col cols="12" sm="12" md="6" v-if="showPassword"> <v-col cols="12" sm="12" md="6" v-if="showPassword">
@ -143,7 +143,7 @@ export default {
}, },
{ text: "Full Name", value: "fullName" }, { text: "Full Name", value: "fullName" },
{ text: "Email", value: "email" }, { text: "Email", value: "email" },
{ text: "Family", value: "family" }, { text: "Group", value: "group" },
{ text: "Admin", value: "admin" }, { text: "Admin", value: "admin" },
{ text: "", value: "actions", sortable: false, align: "center" }, { text: "", value: "actions", sortable: false, align: "center" },
], ],
@ -154,7 +154,7 @@ export default {
fullName: "", fullName: "",
password: "", password: "",
email: "", email: "",
family: "", group: "",
admin: false, admin: false,
}, },
defaultItem: { defaultItem: {
@ -162,7 +162,7 @@ export default {
fullName: "", fullName: "",
password: "", password: "",
email: "", email: "",
family: "", group: "",
admin: false, admin: false,
}, },
}), }),

View file

@ -121,7 +121,7 @@ export default {
const userData = { const userData = {
fullName: this.user.name, fullName: this.user.name,
email: this.user.email, email: this.user.email,
family: "public", group: "default",
password: this.user.password, password: this.user.password,
admin: false, admin: false,
}; };

View file

@ -55,11 +55,11 @@
> >
</v-text-field> </v-text-field>
<v-text-field <v-text-field
label="Family" label="Group"
readonly readonly
v-model="user.family" v-model="user.group"
persistent-hint persistent-hint
hint="Family groups can only be set by administrators" hint="Group groups can only be set by administrators"
> >
</v-text-field> </v-text-field>
</v-form> </v-form>
@ -167,7 +167,7 @@ export default {
user: { user: {
fullName: "Change Me", fullName: "Change Me",
email: "changeme@email.com", email: "changeme@email.com",
family: "public", group: "public",
admin: true, admin: true,
id: 1, id: 1,
}, },

View file

@ -1,5 +1,6 @@
import uvicorn import uvicorn
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.logger import logger
# import utils.startup as startup # import utils.startup as startup
from core.config import APP_VERSION, PORT, SECRET, docs_url, redoc_url from core.config import APP_VERSION, PORT, SECRET, docs_url, redoc_url
@ -13,6 +14,7 @@ from routes import (
setting_routes, setting_routes,
theme_routes, theme_routes,
) )
from routes.groups import groups
from routes.recipe import ( from routes.recipe import (
all_recipe_routes, all_recipe_routes,
category_routes, category_routes,
@ -20,7 +22,6 @@ from routes.recipe import (
tag_routes, tag_routes,
) )
from routes.users import users from routes.users import users
from fastapi.logger import logger
app = FastAPI( app = FastAPI(
title="Mealie", title="Mealie",
@ -42,11 +43,13 @@ def start_scheduler():
def api_routers(): def api_routers():
# Authentication # Authentication
app.include_router(users.router) app.include_router(users.router)
app.include_router(groups.router)
# Recipes # Recipes
app.include_router(all_recipe_routes.router) app.include_router(all_recipe_routes.router)
app.include_router(category_routes.router) app.include_router(category_routes.router)
app.include_router(tag_routes.router) app.include_router(tag_routes.router)
app.include_router(recipe_crud_routes.router) app.include_router(recipe_crud_routes.router)
# Meal Routes # Meal Routes
app.include_router(meal_routes.router) app.include_router(meal_routes.router)
# Settings Routes # Settings Routes

View file

@ -83,6 +83,7 @@ else:
# Mongo Database # Mongo Database
MEALIE_DB_NAME = os.getenv("mealie_db_name", "mealie") MEALIE_DB_NAME = os.getenv("mealie_db_name", "mealie")
DEFAULT_GROUP = os.getenv("default_group", "home")
DB_USERNAME = os.getenv("db_username", "root") DB_USERNAME = os.getenv("db_username", "root")
DB_PASSWORD = os.getenv("db_password", "example") DB_PASSWORD = os.getenv("db_password", "example")
DB_HOST = os.getenv("db_host", "mongo") DB_HOST = os.getenv("db_host", "mongo")

View file

@ -1,6 +1,8 @@
from schema.user import GroupInDB, UserInDB
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
from db.db_base import BaseDocument from db.db_base import BaseDocument
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 SiteSettingsModel
@ -8,16 +10,12 @@ 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
"""
# TODO
- [ ] Abstract Classes to use save_new, and update from base models
"""
class _Recipes(BaseDocument): class _Recipes(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "slug" self.primary_key = "slug"
self.sql_model: RecipeModel = RecipeModel self.sql_model: RecipeModel = RecipeModel
self.orm_mode = False
def update_image(self, session: Session, slug: str, extension: str = None) -> str: def update_image(self, session: Session, slug: str, extension: str = None) -> str:
entry: RecipeModel = self._query_one(session, match_value=slug) entry: RecipeModel = self._query_one(session, match_value=slug)
@ -31,36 +29,43 @@ class _Categories(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "slug" self.primary_key = "slug"
self.sql_model = Category self.sql_model = Category
self.orm_mode = False
class _Tags(BaseDocument): class _Tags(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "slug" self.primary_key = "slug"
self.sql_model = Tag self.sql_model = Tag
self.orm_mode = False
class _Meals(BaseDocument): class _Meals(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "uid" self.primary_key = "uid"
self.sql_model = MealPlanModel self.sql_model = MealPlanModel
self.orm_mode = False
class _Settings(BaseDocument): class _Settings(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "name" self.primary_key = "name"
self.sql_model = SiteSettingsModel self.sql_model = SiteSettingsModel
self.orm_mode = False
class _Themes(BaseDocument): class _Themes(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "name" self.primary_key = "name"
self.sql_model = SiteThemeModel self.sql_model = SiteThemeModel
self.orm_mode = False
class _Users(BaseDocument): class _Users(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "id" self.primary_key = "id"
self.sql_model = User self.sql_model = User
self.orm_mode = True
self.schema = UserInDB
def update_password(self, session, id, password: str): def update_password(self, session, id, password: str):
entry = self._query_one(session=session, match_value=id) entry = self._query_one(session=session, match_value=id)
@ -71,16 +76,19 @@ class _Users(BaseDocument):
return return_data return return_data
class _SignUps(BaseDocument): class _Groups(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "token" self.primary_key = "id"
self.sql_model = SignUp self.sql_model = Group
self.orm_mode = True
self.schema = GroupInDB
class _SignUps(BaseDocument): class _SignUps(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "token" self.primary_key = "token"
self.sql_model = SignUp self.sql_model = SignUp
self.orm_mode = False
class Database: class Database:
@ -93,6 +101,7 @@ class Database:
self.tags = _Tags() self.tags = _Tags()
self.users = _Users() self.users = _Users()
self.sign_ups = _SignUps() self.sign_ups = _SignUps()
self.groups = _Groups()
db = Database() db = Database()

View file

@ -1,5 +1,6 @@
from typing import List from typing import List
from pydantic import BaseModel
from sqlalchemy.orm import load_only from sqlalchemy.orm import load_only
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
@ -11,11 +12,20 @@ class BaseDocument:
self.primary_key: str self.primary_key: str
self.store: str self.store: str
self.sql_model: SqlAlchemyBase self.sql_model: SqlAlchemyBase
self.orm_mode = False
self.schema: BaseModel
# TODO: Improve Get All Query Functionality # TODO: Improve Get All Query Functionality
def get_all( def get_all(
self, session: Session, limit: int = None, order_by: str = None self, session: Session, limit: int = None, order_by: str = None
) -> List[dict]: ) -> List[dict]:
if self.orm_mode:
return [
self.schema.from_orm(x)
for x in session.query(self.sql_model).limit(limit).all()
]
list = [x.dict() for x in session.query(self.sql_model).limit(limit).all()] list = [x.dict() for x in session.query(self.sql_model).limit(limit).all()]
if limit == 1: if limit == 1:
@ -105,6 +115,15 @@ class BaseDocument:
.limit(limit) .limit(limit)
.all() .all()
) )
if self.orm_mode:
if limit == 1:
try:
return self.schema.from_orm(result[0])
except IndexError:
return None
return [self.schema(x) for x in result]
db_entries = [x.dict() for x in result] db_entries = [x.dict() for x in result]
if limit == 1: if limit == 1:
@ -128,6 +147,10 @@ class BaseDocument:
new_document = self.sql_model(session=session, **document) new_document = self.sql_model(session=session, **document)
session.add(new_document) session.add(new_document)
session.commit() session.commit()
if self.orm_mode:
return self.schema.from_orm(new_document)
return_data = new_document.dict() return_data = new_document.dict()
return return_data return return_data
@ -145,9 +168,13 @@ class BaseDocument:
entry = self._query_one(session=session, match_value=match_value) entry = self._query_one(session=session, match_value=match_value)
entry.update(session=session, **new_data) entry.update(session=session, **new_data)
if self.orm_mode:
session.commit()
return self.schema.from_orm(entry)
return_data = entry.dict() return_data = entry.dict()
session.commit() session.commit()
return return_data return return_data
def delete(self, session: Session, primary_key_value) -> dict: def delete(self, session: Session, primary_key_value) -> dict:

View file

@ -1,3 +1,4 @@
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, Webhooks
@ -12,6 +13,7 @@ def init_db(db: Session = None) -> None:
if not db: if not db:
db = create_session() db = create_session()
default_group_init(db)
default_settings_init(db) default_settings_init(db)
default_theme_init(db) default_theme_init(db)
default_user_init(db) default_user_init(db)
@ -50,12 +52,19 @@ def default_settings_init(session: Session):
pass pass
def default_group_init(session: Session):
default_group = {"name": DEFAULT_GROUP}
logger.info("Generating Default Group")
db.groups.create(session, default_group)
pass
def default_user_init(session: Session): def default_user_init(session: Session):
default_user = { default_user = {
"full_name": "Change Me", "full_name": "Change Me",
"email": "changeme@email.com", "email": "changeme@email.com",
"password": get_password_hash("MyPassword"), "password": get_password_hash("MyPassword"),
"family": "public", "group": DEFAULT_GROUP,
"admin": True, "admin": True,
} }

View file

@ -4,3 +4,4 @@ from db.models.settings import *
from db.models.theme import * from db.models.theme import *
from db.models.users import * from db.models.users import *
from db.models.sign_up import * from db.models.sign_up import *
from db.models.group import *

View file

@ -1,41 +1,31 @@
import sqlalchemy as sa
import sqlalchemy.orm as orm
from core.config import DEFAULT_GROUP
from db.models.model_base import BaseMixins, SqlAlchemyBase from db.models.model_base import BaseMixins, SqlAlchemyBase
from sqlalchemy import Boolean, Column, Integer, String from fastapi.logger import logger
from slugify import slugify
class Group(SqlAlchemyBase, BaseMixins): class Group(SqlAlchemyBase, BaseMixins):
__tablename__ = "groups" __tablename__ = "groups"
id = Column(Integer, primary_key=True) id = sa.Column(sa.Integer, primary_key=True)
name = Column(String, index=True) name = sa.Column(sa.String, index=True, nullable=False, unique=True)
slug = Column(String, unique=True, index=True) users = orm.relationship("User", back_populates="group")
mealplans = orm.relationship("MealPlanModel", back_populates="group")
def __init__( def __init__(self, name, session=None) -> None:
self, self.name = name
session,
full_name,
email,
password,
family="public",
admin=False,
) -> None:
self.full_name = full_name
self.email = email
self.family = family
self.admin = admin
self.password = password
def dict(self):
return {
"id": self.id,
"full_name": self.full_name,
"email": self.email,
"admin": self.admin,
"family": self.family,
"password": self.password,
}
def update(self, full_name, email, family, admin, session=None):
self.full_name = full_name
self.email = email
self.family = family
self.admin = admin
@staticmethod
def create_if_not_exist(session, name: str = DEFAULT_GROUP):
try:
result = session.query(Group).filter(Group.name == name).one()
if result:
logger.info("Category exists, associating recipe")
return result
else:
logger.info("Category doesn't exists, creating tag")
return Group(name=name)
except:
logger.info("Category doesn't exists, creating category")
return Group(name=name)

View file

@ -46,6 +46,8 @@ class MealPlanModel(SqlAlchemyBase, BaseMixins):
startDate = sa.Column(sa.Date) startDate = sa.Column(sa.Date)
endDate = sa.Column(sa.Date) endDate = sa.Column(sa.Date)
meals: List[Meal] = orm.relation(Meal) meals: List[Meal] = orm.relation(Meal)
group_id = sa.Column(sa.String, sa.ForeignKey("groups.id"))
group = orm.relationship("Group", back_populates="mealplans")
def __init__(self, startDate, endDate, meals, uid=None, session=None) -> None: def __init__(self, startDate, endDate, meals, uid=None, session=None) -> None:
self.startDate = startDate self.startDate = startDate

View file

@ -6,12 +6,12 @@ class Nutrition(SqlAlchemyBase):
__tablename__ = "recipe_nutrition" __tablename__ = "recipe_nutrition"
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")) parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
calories = sa.Column(sa.Integer) calories = sa.Column(sa.String)
fatContent = sa.Column(sa.Integer) fatContent = sa.Column(sa.String)
fiberContent = sa.Column(sa.Integer) fiberContent = sa.Column(sa.String)
proteinContent = sa.Column(sa.Integer) proteinContent = sa.Column(sa.String)
sodiumContent = sa.Column(sa.Integer) sodiumContent = sa.Column(sa.String)
sugarContent = sa.Column(sa.Integer) sugarContent = sa.Column(sa.String)
def __init__( def __init__(
self, self,

View file

@ -1,5 +1,7 @@
from core.config import DEFAULT_GROUP
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, Integer, String from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, orm
class User(SqlAlchemyBase, BaseMixins): class User(SqlAlchemyBase, BaseMixins):
@ -8,9 +10,9 @@ class User(SqlAlchemyBase, BaseMixins):
full_name = Column(String, index=True) full_name = Column(String, index=True)
email = Column(String, unique=True, index=True) email = Column(String, unique=True, index=True)
password = Column(String) password = Column(String)
is_active = Column(Boolean(), default=True) group_id = Column(String, ForeignKey("groups.id"))
family = Column(String) group = orm.relationship("Group", back_populates="users")
admin = Column(Boolean(), default=False) admin = Column(Boolean, default=False)
def __init__( def __init__(
self, self,
@ -18,29 +20,21 @@ class User(SqlAlchemyBase, BaseMixins):
full_name, full_name,
email, email,
password, password,
family="public", group: str = DEFAULT_GROUP,
admin=False, admin=False,
) -> None: ) -> None:
group = group if group else DEFAULT_GROUP
self.full_name = full_name self.full_name = full_name
self.email = email self.email = email
self.family = family self.group = Group.create_if_not_exist(session, group)
self.admin = admin self.admin = admin
self.password = password self.password = password
def dict(self): def update(self, full_name, email, group, admin, session=None):
return {
"id": self.id,
"full_name": self.full_name,
"email": self.email,
"admin": self.admin,
"family": self.family,
"password": self.password,
}
def update(self, full_name, email, family, admin, session=None):
self.full_name = full_name self.full_name = full_name
self.email = email self.email = email
self.family = family self.group = Group.create_if_not_exist(session, group)
self.admin = admin self.admin = admin
def update_password(self, password): def update_password(self, password):

View file

@ -20,5 +20,5 @@ def query_user(user_email: str, session: Session = None) -> UserInDB:
session = session if session else create_session() session = session if session else create_session()
user = db.users.get(session, user_email, "email") user = db.users.get(session, user_email, "email")
session.close() session.close()
return UserInDB(**user) return user

View file

@ -0,0 +1,24 @@
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 routes.deps import manager
from schema.snackbar import SnackResponse
from schema.user import GroupBase, GroupInDB
from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/api/groups", tags=["Groups"])
@router.get("", response_model=list[GroupInDB])
async def get_all_groups(
current_user=Depends(manager),
session: Session = Depends(generate_session),
):
""" Returns a list of all groups in the database """
return db.groups.get_all(session)

View file

@ -0,0 +1,7 @@
from fastapi import APIRouter
from routes.groups import crud
router = APIRouter()
router.include_router(crud.router)

View file

@ -66,8 +66,8 @@ async def update_user(
): ):
if current_user.id == id or current_user.admin: if current_user.id == id or current_user.admin:
updated_user = db.users.update(session, id, new_data.dict()) updated_user: UserInDB = db.users.update(session, id, new_data.dict())
email = updated_user.get("email") email = updated_user.email
if current_user.id == id: if current_user.id == id:
access_token = manager.create_access_token( access_token = manager.create_access_token(
data=dict(sub=email), expires=timedelta(hours=2) data=dict(sub=email), expires=timedelta(hours=2)

View file

@ -15,12 +15,12 @@ class RecipeStep(BaseModel):
class Nutrition(BaseModel): class Nutrition(BaseModel):
calories: Optional[int] calories: Optional[str]
fatContent: Optional[int] fatContent: Optional[str]
fiberContent: Optional[int] fiberContent: Optional[str]
proteinContent: Optional[int] proteinContent: Optional[str]
sodiumContent: Optional[int] sodiumContent: Optional[str]
sugarContent: Optional[int] sugarContent: Optional[str]
class Recipe(BaseModel): class Recipe(BaseModel):

View file

@ -1,26 +1,35 @@
from typing import Optional from typing import Optional
from core.config import DEFAULT_GROUP
from fastapi_camelcase import CamelModel from fastapi_camelcase import CamelModel
# from pydantic import EmailStr
class ChangePassword(CamelModel): class ChangePassword(CamelModel):
current_password: str current_password: str
new_password: str new_password: str
class GroupBase(CamelModel):
name: str
class Config:
orm_mode = True
class UserBase(CamelModel): class UserBase(CamelModel):
full_name: Optional[str] = None full_name: Optional[str] = None
email: str email: str
family: str
admin: bool admin: bool
group: Optional[str]
class Config:
orm_mode = True
class Config: class Config:
schema_extra = { schema_extra = {
"fullName": "Change Me", "fullName": "Change Me",
"email": "changeme@email.com", "email": "changeme@email.com",
"family": "public", "group": DEFAULT_GROUP,
"admin": "false", "admin": "false",
} }
@ -31,7 +40,24 @@ class UserIn(UserBase):
class UserOut(UserBase): class UserOut(UserBase):
id: int id: int
group: GroupBase
class Config:
orm_mode = True
class UserInDB(UserIn, UserOut): class UserInDB(UserOut):
password: str
pass pass
class Config:
orm_mode = True
class GroupInDB(GroupBase):
id: int
name: str
users: Optional[list[UserOut]]
class Config:
orm_mode = True

View file

@ -20,12 +20,12 @@ class RecipeStep(BaseModel):
class Nutrition(BaseModel): class Nutrition(BaseModel):
calories: Optional[int] calories: Optional[str]
fatContent: Optional[int] fatContent: Optional[str]
fiberContent: Optional[int] fiberContent: Optional[str]
proteinContent: Optional[int] proteinContent: Optional[str]
sodiumContent: Optional[int] sodiumContent: Optional[str]
sugarContent: Optional[int] sugarContent: Optional[str]
class Recipe(BaseModel): class Recipe(BaseModel):

View file

@ -31,6 +31,9 @@ class Cleaner:
recipe_data["prepTime"] = Cleaner.time(recipe_data.get("prepTime", None)) recipe_data["prepTime"] = Cleaner.time(recipe_data.get("prepTime", None))
recipe_data["performTime"] = Cleaner.time(recipe_data.get("performTime", None)) recipe_data["performTime"] = Cleaner.time(recipe_data.get("performTime", None))
recipe_data["totalTime"] = Cleaner.time(recipe_data.get("totalTime", None)) recipe_data["totalTime"] = Cleaner.time(recipe_data.get("totalTime", None))
recipe_data["recipeCategory"] = Cleaner.category(
recipe_data.get("recipeCategory", [])
)
recipe_data["recipeYield"] = Cleaner.yield_amount( recipe_data["recipeYield"] = Cleaner.yield_amount(
recipe_data.get("recipeYield") recipe_data.get("recipeYield")
@ -47,6 +50,13 @@ class Cleaner:
return recipe_data return recipe_data
@staticmethod
def category(category: str):
if type(category) == type(str):
return [category]
else:
return []
@staticmethod @staticmethod
def html(raw_html): def html(raw_html):
cleanr = re.compile("<.*?>") cleanr = re.compile("<.*?>")

View file

@ -34,7 +34,7 @@ def api_client():
yield TestClient(app) yield TestClient(app)
SQLITE_FILE.unlink() # SQLITE_FILE.unlink()
@fixture(scope="session") @fixture(scope="session")

View file

@ -63,7 +63,7 @@ def test_read_update(api_client, recipe_data):
recipe["notes"] = test_notes recipe["notes"] = test_notes
test_categories = ["one", "two", "three"] test_categories = ["one", "two", "three"]
recipe["categories"] = test_categories recipe["recipeCategory"] = test_categories
response = api_client.put( response = api_client.put(
f"{RECIPES_PREFIX}/{recipe_data.expected_slug}", json=recipe f"{RECIPES_PREFIX}/{recipe_data.expected_slug}", json=recipe
@ -77,7 +77,7 @@ def test_read_update(api_client, recipe_data):
recipe = json.loads(response.content) recipe = json.loads(response.content)
assert recipe["notes"] == test_notes assert recipe["notes"] == test_notes
assert recipe["categories"].sort() == test_categories.sort() assert recipe["recipeCategory"].sort() == test_categories.sort()
@pytest.mark.parametrize("recipe_data", recipe_test_data) @pytest.mark.parametrize("recipe_data", recipe_test_data)

View file

@ -16,7 +16,9 @@ def default_user():
"id": 1, "id": 1,
"fullName": "Change Me", "fullName": "Change Me",
"email": "changeme@email.com", "email": "changeme@email.com",
"family": "public", "group": {
"name": "home"
},
"admin": True "admin": True
} }
@ -27,7 +29,9 @@ def new_user():
"id": 2, "id": 2,
"fullName": "My New User", "fullName": "My New User",
"email": "newuser@email.com", "email": "newuser@email.com",
"family": "public", "group": {
"name": "home"
},
"admin": False "admin": False
} }
@ -54,7 +58,7 @@ def test_create_user(api_client: requests, token, new_user):
"fullName": "My New User", "fullName": "My New User",
"email": "newuser@email.com", "email": "newuser@email.com",
"password": "MyStrongPassword", "password": "MyStrongPassword",
"family": "public", "group": "home",
"admin": False "admin": False
} }
@ -78,7 +82,7 @@ def test_update_user(api_client: requests, token):
"id": 1, "id": 1,
"fullName": "Updated Name", "fullName": "Updated Name",
"email": "updated@email.com", "email": "updated@email.com",
"family": "public", "group": "home",
"admin": True "admin": True
} }
response = api_client.put(f"{BASE}/1", headers=token, json=update_data) response = api_client.put(f"{BASE}/1", headers=token, json=update_data)