Feature/authentication (#207)

* basic crud NOT SECURE

* refactor/database init on startup

* added scratch.py

* tests/user CRUD routes

* password hashing

* change app_config location

* bump python version

* formatting

* login ui starter

* change import from url design

* move components

* remove old snackbar

* refactor/Componenet folder structure rework

* refactor/remove old code

* refactor/rename componenets/js files

* remove console.logs

* refactor/ models to schema and sql to models

* new header styling for imports

* token request

* fix url scrapper

* refactor/rename schema files

* split routes file

* redesigned admin page

* enable relative imports for vue components

* refactor/switch to pages view

* add CamelCase package

* majors settings rework

* user management second pass

* super user CRUD

* refactor/consistent models names

* refactor/consistent model names

* password reset

* store refactor

* dependency update

* abstract button props

* profile page refactor

* basic password validation

* login form refactor/split v-container

* remo unused code

* hide editor buttons when not logged in

* mkdocs dev dependency

* v0.4.0 docs update

* profile image upload

* additional token routes

* Smaller recipe cards for smaller viewports

* fix admin sidebar

* add users

* change to outlined

* theme card starter

* code cleanup

* signups

* signup pages

* fix #194

* fix #193

* clarify mealie_port

* fix #184

* fixes #178

* fix blank card error on meal-plan creator

* admin signup

* formatting

* improved search bar

* improved search bar

* refresh token on page refresh

* allow mealplan with no categories

* fix card layout

* remove cdn dependencies

* start on groups

* Fixes #196

* recipe databse refactor

* changelog draft

* database refactoring

* refactor recipe schema/model

* site settings refactor

* continued model refactor

* merge docs changes from master

* site-settings work

* cleanup + tag models

* notes

* typo

* user table

* sign up data validation

* package updates

* group store init

* Fix home page settings

* group admin init

* group dashboard init

* update deps

* formatting

* bug / added libffi-dev

* pages refactor

* fix mealplan

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-03-13 22:06:19 -09:00 committed by GitHub
commit b6b014f515
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 159 additions and 120 deletions

View file

@ -79,6 +79,7 @@ export default {
this.$store.dispatch("requestHomePageSettings");
this.$store.dispatch("requestSiteSettings");
this.$store.dispatch("refreshToken");
this.$store.dispatch("requestCurrentGroup");
this.darkModeSystemCheck();
this.darkModeAddEventListener();
},

View file

@ -82,6 +82,7 @@
</template>
<script>
const CREATE_EVENT = "created";
import api from "@/api";
import utils from "@/utils";
import MealPlanCard from "./MealPlanCard";
@ -116,9 +117,8 @@ export default {
},
},
async mounted() {
let settings = await api.settings.requestAll();
this.items = await api.recipes.getAllByCategory(settings.planCategories);
console.log(this.items);
let categories = Array.from(this.groupSettings, x => x.name);
this.items = await api.recipes.getAllByCategory(categories);
if (this.items.length === 0) {
const keys = [
@ -134,6 +134,9 @@ export default {
},
computed: {
groupSettings() {
return this.$store.getters.getCurrentGroup;
},
actualStartDate() {
return Date.parse(this.startDate);
},
@ -146,7 +149,6 @@ export default {
let dateDif = (endDate - startDate) / (1000 * 3600 * 24) + 1;
if (dateDif < 1) {
return null;
}
@ -190,12 +192,13 @@ export default {
async save() {
const mealBody = {
group: this.groupSettings.name,
startDate: this.startDate,
endDate: this.endDate,
meals: this.meals,
};
await api.mealPlans.create(mealBody);
this.$emit("created");
this.$emit(CREATE_EVENT);
this.meals = [];
this.startDate = null;
this.endDate = null;

View file

@ -165,11 +165,11 @@ export default {
showPassword: false,
loading: false,
user: {
fullName: "Change Me",
email: "changeme@email.com",
group: "public",
admin: true,
id: 1,
fullName: "",
email: "",
group: "",
admin: false,
id: 0,
},
};
},

View file

@ -87,9 +87,9 @@
<script>
import api from "@/api";
import utils from "@/utils";
import NewMeal from "../components/MealPlan/MealPlanNew";
import EditPlan from "../components/MealPlan/MealPlanEditor";
import ShoppingListDialog from "../components/MealPlan/ShoppingListDialog";
import NewMeal from "@/components/MealPlan/MealPlanNew";
import EditPlan from "@/components/MealPlan/MealPlanEditor";
import ShoppingListDialog from "@/components/MealPlan/ShoppingListDialog";
export default {
components: {

View file

@ -18,7 +18,9 @@
<v-card-title class="justify-center">
{{ meal.name }}
</v-card-title>
<v-card-subtitle> {{ $d(new Date(meal.date), 'short' ) }}</v-card-subtitle>
<v-card-subtitle>
{{ $d(new Date(meal.date), "short") }}</v-card-subtitle
>
<v-card-text> {{ meal.description }} </v-card-text>

View file

@ -43,9 +43,9 @@
<script>
import api from "@/api";
import RecipeEditor from "../components/Recipe/RecipeEditor";
import RecipeEditor from "@/components/Recipe/RecipeEditor";
import VJsoneditor from "v-jsoneditor";
import EditorButtonRow from "../components/Recipe/EditorButtonRow";
import EditorButtonRow from "@/components/Recipe/EditorButtonRow";
export default {
components: {
VJsoneditor,

View file

@ -62,10 +62,10 @@
import api from "@/api";
import utils from "@/utils";
import VJsoneditor from "v-jsoneditor";
import RecipeViewer from "../components/Recipe/RecipeViewer";
import RecipeEditor from "../components/Recipe/RecipeEditor";
import RecipeTimeCard from "../components/Recipe/RecipeTimeCard.vue";
import EditorButtonRow from "../components/Recipe/EditorButtonRow";
import RecipeViewer from "@/components/Recipe/RecipeViewer";
import RecipeEditor from "@/components/Recipe/RecipeEditor";
import RecipeTimeCard from "@/components/Recipe/RecipeTimeCard.vue";
import EditorButtonRow from "@/components/Recipe/EditorButtonRow";
import { user } from "@/mixins/user";
export default {

View file

@ -13,8 +13,8 @@
</template>
<script>
import CardSection from "../components/UI/CardSection";
import CategorySidebar from "../components/UI/CategorySidebar";
import CardSection from "@/components/UI/CardSection";
import CategorySidebar from "@/components/UI/CategorySidebar";
export default {
components: {
CardSection,

View file

@ -14,8 +14,8 @@
<script>
import api from "@/api";
import CardSection from "../components/UI/CardSection";
import CategorySidebar from "../components/UI/CategorySidebar";
import CardSection from "@/components/UI/CardSection";
import CategorySidebar from "@/components/UI/CategorySidebar";
export default {
components: {
CardSection,

View file

@ -1,15 +1,15 @@
import HomePage from "../pages/HomePage";
import Page404 from "../pages/404Page";
import SearchPage from "../pages/SearchPage";
import RecipePage from "../pages/RecipePage";
import RecipeNewPage from "../pages/RecipeNewPage";
import AllRecipesPage from "../pages/AllRecipesPage";
import CategoryPage from "../pages/CategoryPage";
import MeaplPlanPage from "../pages/MealPlanPage";
import Debug from "../pages/Debug";
import LoginPage from "../pages/LoginPage";
import SignUpPage from "../pages/SignUpPage";
import MealPlanThisWeekPage from "../pages/MealPlanThisWeekPage";
import HomePage from "@/pages/HomePage";
import Page404 from "@/pages/404Page";
import SearchPage from "@/pages/SearchPage";
import ViewRecipe from "@/pages/Recipe/ViewRecipe";
import NewRecipe from "@/pages/Recipe/NewRecipe";
import AllRecipes from "@/pages/Recipes/AllRecipes";
import CategoryPage from "@/pages/Recipes/CategoryPage";
import Planner from "@/pages/MealPlan/Planner";
import Debug from "@/pages/Debug";
import LoginPage from "@/pages/LoginPage";
import SignUpPage from "@/pages/SignUpPage";
import ThisWeek from "@/pages/MealPlan/ThisWeek";
import api from "@/api";
import Admin from "./admin";
import { store } from "../store";
@ -30,12 +30,12 @@ export const routes = [
{ path: "/sign-up/:token", component: SignUpPage },
{ path: "/debug", component: Debug },
{ path: "/search", component: SearchPage },
{ path: "/recipes/all", component: AllRecipesPage },
{ path: "/recipes/all", component: AllRecipes },
{ path: "/recipes/:category", component: CategoryPage },
{ path: "/recipe/:recipe", component: RecipePage },
{ path: "/new/", component: RecipeNewPage },
{ path: "/meal-plan/planner", component: MeaplPlanPage },
{ path: "/meal-plan/this-week", component: MealPlanThisWeekPage },
{ path: "/recipe/:recipe", component: ViewRecipe },
{ path: "/new/", component: NewRecipe },
{ path: "/meal-plan/planner", component: Planner },
{ path: "/meal-plan/this-week", component: ThisWeek },
Admin,
{
path: "/meal-plan/today",

View file

@ -28,7 +28,7 @@ const actions = {
const getters = {
getGroups: state => state.groups,
getGroupNames: state => Array.from(state.groups, x => x.name),
getCurrentGroup: state => state.currentGroup
getCurrentGroup: state => state.currentGroup,
};
export default {

View file

@ -9,12 +9,12 @@ from db.init_db import init_db
from routes import (
backup_routes,
debug_routes,
meal_routes,
migration_routes,
setting_routes,
theme_routes,
)
from routes.groups import groups
from routes.mealplans import mealplans
from routes.recipe import (
all_recipe_routes,
category_routes,
@ -51,7 +51,7 @@ def api_routers():
app.include_router(recipe_crud_routes.router)
# Meal Routes
app.include_router(meal_routes.router)
app.include_router(mealplans.router)
# Settings Routes
app.include_router(setting_routes.router)
app.include_router(theme_routes.router)

View file

@ -3,6 +3,9 @@ from pathlib import Path
import dotenv
APP_VERSION = "v0.3.0"
DB_VERSION = "v0.3.0"
CWD = Path(__file__).parent
@ -19,8 +22,6 @@ dotenv.load_dotenv(ENV)
SECRET = "super-secret-key"
# General
APP_VERSION = "v0.3.0"
DB_VERSION = "v0.3.0"
PRODUCTION = os.environ.get("ENV")
PORT = int(os.getenv("mealie_port", 9000))
API = os.getenv("api_docs", True)
@ -83,7 +84,7 @@ else:
# Mongo Database
MEALIE_DB_NAME = os.getenv("mealie_db_name", "mealie")
DEFAULT_GROUP = os.getenv("default_group", "home")
DEFAULT_GROUP = os.getenv("default_group", "Home")
DB_USERNAME = os.getenv("db_username", "root")
DB_PASSWORD = os.getenv("db_password", "example")
DB_HOST = os.getenv("db_host", "mongo")

View file

@ -60,6 +60,10 @@ class Group(SqlAlchemyBase, BaseMixins):
self.__init__(session=session, *args, **kwargs)
@staticmethod
def get_ref(session: Session, name: str):
return session.query(Group).filter(Group.name == name).one()
@staticmethod
def create_if_not_exist(session, name: str = DEFAULT_GROUP):
try:

View file

@ -1,8 +1,8 @@
import uuid
from typing import List
import sqlalchemy as sa
import sqlalchemy.orm as orm
from db.models.group import Group
from db.models.model_base import BaseMixins, SqlAlchemyBase
@ -29,16 +29,25 @@ class MealPlanModel(SqlAlchemyBase, BaseMixins):
uid = sa.Column(sa.Integer, primary_key=True, unique=True) #! Probably Bad?
startDate = sa.Column(sa.Date)
endDate = sa.Column(sa.Date)
meals: List[Meal] = orm.relation(Meal)
meals: List[Meal] = orm.relationship(Meal, cascade="all, delete")
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, group: str, uid=None, session=None
) -> None:
self.startDate = startDate
self.endDate = endDate
self.group = Group.get_ref(session, group)
self.meals = [Meal(**meal) for meal in meals]
def update(self, session, startDate, endDate, meals, uid) -> None:
def update(self, session, startDate, endDate, meals, uid, group) -> None:
MealPlanModel._sql_remove_list(session, [Meal], uid)
self.__init__(startDate, endDate, meals)
self.__init__(
startDate=startDate,
endDate=endDate,
meals=meals,
group=group,
session=session,
)

View file

@ -1,44 +1,33 @@
from datetime import date
from typing import List
from db.database import db
from db.db_setup import generate_session
from fastapi import APIRouter, Depends
from schema.meal import MealPlanBase, MealPlanInDB
from schema.recipe import Recipe
from routes.deps import manager
from schema.meal import MealPlanIn, MealPlanInDB
from schema.snackbar import SnackResponse
from schema.user import GroupInDB, UserInDB
from services.meal_services import get_todays_meal, process_meals
from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
@router.get("/all", response_model=List[MealPlanInDB])
def get_all_meals(session: Session = Depends(generate_session)):
@router.get("/all", response_model=list[MealPlanInDB])
def get_all_meals(
current_user: UserInDB = Depends(manager),
session: Session = Depends(generate_session),
):
""" Returns a list of all available Meal Plan """
return db.meals.get_all(session)
@router.get("/{id}/shopping-list")
def get_shopping_list(id: str, session: Session = Depends(generate_session)):
#! Refactor into Single Database Call
mealplan = db.meals.get(session, id)
mealplan: MealPlanInDB
slugs = [x.slug for x in mealplan.meals]
recipes: list[Recipe] = [db.recipes.get(session, x) for x in slugs]
ingredients = [
{"name": x.name, "recipeIngredient": x.recipeIngredient}
for x in recipes
if x
]
return ingredients
print(current_user.group)
group_entry: GroupInDB = db.groups.get(session, current_user.group, "name")
return group_entry.mealplans
@router.post("/create")
def create_meal_plan(data: MealPlanBase, session: Session = Depends(generate_session)):
def create_meal_plan(
data: MealPlanIn,
session: Session = Depends(generate_session),
current_user=Depends(manager),
):
""" Creates a meal plan database entry """
processed_plan = process_meals(session, data)
db.meals.create(session, processed_plan.dict())
@ -46,16 +35,9 @@ def create_meal_plan(data: MealPlanBase, session: Session = Depends(generate_ses
return SnackResponse.success("Mealplan Created")
@router.get("/this-week", response_model=MealPlanInDB)
def get_this_week(session: Session = Depends(generate_session)):
""" Returns the meal plan data for this week """
return db.meals.get_all(session, limit=1, order_by="startDate")
@router.put("/{plan_id}")
def update_meal_plan(
plan_id: str, meal_plan: MealPlanBase, session: Session = Depends(generate_session)
plan_id: str, meal_plan: MealPlanIn, session: Session = Depends(generate_session)
):
""" Updates a meal plan based off ID """
processed_plan = process_meals(session, meal_plan)
@ -74,6 +56,13 @@ def delete_meal_plan(plan_id, session: Session = Depends(generate_session)):
return SnackResponse.error("Mealplan Deleted")
@router.get("/this-week", response_model=MealPlanInDB)
def get_this_week(session: Session = Depends(generate_session)):
""" Returns the meal plan data for this week """
return db.meals.get_all(session, limit=1, order_by="startDate")
@router.get("/today", tags=["Meal Plan"])
def get_today(session: Session = Depends(generate_session)):
"""

View file

@ -0,0 +1,23 @@
from db.database import db
from db.db_setup import generate_session
from fastapi import APIRouter, Depends
from schema.meal import MealPlanInDB
from schema.recipe import Recipe
from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
@router.get("/{id}/shopping-list")
def get_shopping_list(id: str, session: Session = Depends(generate_session)):
#! Refactor into Single Database Call
mealplan = db.meals.get(session, id)
mealplan: MealPlanInDB
slugs = [x.slug for x in mealplan.meals]
recipes: list[Recipe] = [db.recipes.get(session, x) for x in slugs]
ingredients = [
{"name": x.name, "recipeIngredient": x.recipeIngredient} for x in recipes if x
]
return ingredients

View file

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

View file

@ -1,7 +1,9 @@
from datetime import date
from typing import List, Optional
from db.models.mealplan import MealPlanModel
from pydantic import BaseModel, validator
from pydantic.utils import GetterDict
class MealIn(BaseModel):
@ -18,7 +20,8 @@ class MealOut(MealIn):
orm_mode = True
class MealPlanBase(BaseModel):
class MealPlanIn(BaseModel):
group: str
startDate: date
endDate: date
meals: List[MealIn]
@ -30,7 +33,7 @@ class MealPlanBase(BaseModel):
return v
class MealPlanProcessed(MealPlanBase):
class MealPlanProcessed(MealPlanIn):
meals: list[MealOut]
@ -40,18 +43,9 @@ class MealPlanInDB(MealPlanProcessed):
class Config:
orm_mode = True
class MealPlan(BaseModel):
uid: Optional[str]
class Config:
schema_extra = {
"example": {
"startDate": date.today(),
"endDate": date.today(),
"meals": [
{"slug": "Packed Mac and Cheese", "date": date.today()},
{"slug": "Eggs and Toast", "date": date.today()},
],
@classmethod
def getter_dict(_cls, name_orm: MealPlanModel):
return {
**GetterDict(name_orm),
"group": name_orm.group.name,
}
}

View file

@ -1,12 +1,12 @@
from datetime import date, timedelta
from db.database import db
from schema.meal import MealIn, MealOut, MealPlanBase, MealPlanProcessed
from schema.meal import MealIn, MealOut, MealPlanIn, MealPlanProcessed
from schema.recipe import Recipe
from sqlalchemy.orm.session import Session
def process_meals(session: Session, meal_plan_base: MealPlanBase) -> MealPlanProcessed:
def process_meals(session: Session, meal_plan_base: MealPlanIn) -> MealPlanProcessed:
meals = []
for x, meal in enumerate(meal_plan_base.meals):
meal: MealIn
@ -30,7 +30,10 @@ def process_meals(session: Session, meal_plan_base: MealPlanBase) -> MealPlanPro
meals.append(meal_data)
return MealPlanProcessed(
meals=meals, startDate=meal_plan_base.startDate, endDate=meal_plan_base.endDate
group=meal_plan_base.group,
meals=meals,
startDate=meal_plan_base.startDate,
endDate=meal_plan_base.endDate,
)

View file

@ -13,6 +13,7 @@ from tests.utils.routes import (
def get_meal_plan_template(first=None, second=None):
return {
"group": "Home",
"startDate": "2021-01-18",
"endDate": "2021-01-19",
"meals": [
@ -54,17 +55,17 @@ def slug_2(api_client):
api_client.delete(RECIPES_PREFIX + "/" + slug_2)
def test_create_mealplan(api_client, slug_1, slug_2):
def test_create_mealplan(api_client, slug_1, slug_2, token):
meal_plan = get_meal_plan_template()
meal_plan["meals"][0]["slug"] = slug_1
meal_plan["meals"][1]["slug"] = slug_2
response = api_client.post(MEALPLAN_CREATE, json=meal_plan)
response = api_client.post(MEALPLAN_CREATE, json=meal_plan, headers=token)
assert response.status_code == 200
def test_read_mealplan(api_client, slug_1, slug_2):
response = api_client.get(MEALPLAN_ALL)
def test_read_mealplan(api_client, slug_1, slug_2, token):
response = api_client.get(MEALPLAN_ALL, headers=token)
assert response.status_code == 200
@ -77,9 +78,9 @@ def test_read_mealplan(api_client, slug_1, slug_2):
assert meals[1]["slug"] == meal_plan["meals"][1]["slug"]
def test_update_mealplan(api_client, slug_1, slug_2):
def test_update_mealplan(api_client, slug_1, slug_2, token):
response = api_client.get(MEALPLAN_ALL)
response = api_client.get(MEALPLAN_ALL, headers=token)
existing_mealplan = json.loads(response.text)
existing_mealplan = existing_mealplan[0]
@ -89,11 +90,13 @@ def test_update_mealplan(api_client, slug_1, slug_2):
existing_mealplan["meals"][0]["slug"] = slug_2
existing_mealplan["meals"][1]["slug"] = slug_1
response = api_client.put(f"{MEALPLAN_PREFIX}/{plan_uid}", json=existing_mealplan)
response = api_client.put(
f"{MEALPLAN_PREFIX}/{plan_uid}", json=existing_mealplan, headers=token
)
assert response.status_code == 200
response = api_client.get(MEALPLAN_ALL)
response = api_client.get(MEALPLAN_ALL, headers=token)
existing_mealplan = json.loads(response.text)
existing_mealplan = existing_mealplan[0]
@ -101,8 +104,8 @@ def test_update_mealplan(api_client, slug_1, slug_2):
assert existing_mealplan["meals"][1]["slug"] == slug_1
def test_delete_mealplan(api_client):
response = api_client.get(MEALPLAN_ALL)
def test_delete_mealplan(api_client, token):
response = api_client.get(MEALPLAN_ALL, headers=token)
assert response.status_code == 200
existing_mealplan = json.loads(response.text)

View file

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