feature/recipe-comments (#448)

* fix favorite color issue

* db and models for comments

* rename files

* initial UI for comments

* fix format

* import / export

* fixes #428

* format

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-05-29 20:50:17 -08:00 committed by GitHub
parent 6f38fcf81b
commit 2b97af5728
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 360 additions and 19 deletions

View File

@ -1,4 +1,3 @@
import json
import re import re
from enum import Enum from enum import Enum
from itertools import groupby from itertools import groupby
@ -81,8 +80,6 @@ class PathObject(BaseModel):
def get_path_objects(app: FastAPI): def get_path_objects(app: FastAPI):
paths = [] paths = []
with open("scratch.json", "w") as f:
f.write(json.dumps(app.openapi()))
for key, value in app.openapi().items(): for key, value in app.openapi().items():
if key == "paths": if key == "paths":
for key, value in value.items(): for key, value in value.items():

View File

@ -107,6 +107,12 @@ class AppRoutes:
def recipes_recipe_slug_image(self, recipe_slug): def recipes_recipe_slug_image(self, recipe_slug):
return f"{self.prefix}/recipes/{recipe_slug}/image" return f"{self.prefix}/recipes/{recipe_slug}/image"
def recipes_slug_comments(self, slug):
return f"{self.prefix}/recipes/{slug}/comments"
def recipes_slug_comments_id(self, slug, id):
return f"{self.prefix}/recipes/{slug}/comments/{id}"
def shopping_lists_id(self, id): def shopping_lists_id(self, id):
return f"{self.prefix}/shopping-lists/{id}" return f"{self.prefix}/shopping-lists/{id}"
@ -126,7 +132,7 @@ class AppRoutes:
return f"{self.prefix}/users/{id}" return f"{self.prefix}/users/{id}"
def users_id_favorites(self, id): def users_id_favorites(self, id):
return f"{self.prefix}/users/{id}/favorites/" return f"{self.prefix}/users/{id}/favorites"
def users_id_favorites_slug(self, id, slug): def users_id_favorites_slug(self, id, slug):
return f"{self.prefix}/users/{id}/favorites/{slug}" return f"{self.prefix}/users/{id}/favorites/{slug}"

View File

@ -65,6 +65,8 @@ export const API_ROUTES = {
recipesRecipeSlug: (recipe_slug) => `${prefix}/recipes/${recipe_slug}`, recipesRecipeSlug: (recipe_slug) => `${prefix}/recipes/${recipe_slug}`,
recipesRecipeSlugAssets: (recipe_slug) => `${prefix}/recipes/${recipe_slug}/assets`, recipesRecipeSlugAssets: (recipe_slug) => `${prefix}/recipes/${recipe_slug}/assets`,
recipesRecipeSlugImage: (recipe_slug) => `${prefix}/recipes/${recipe_slug}/image`, recipesRecipeSlugImage: (recipe_slug) => `${prefix}/recipes/${recipe_slug}/image`,
recipesSlugComments: (slug) => `${prefix}/recipes/${slug}/comments`,
recipesSlugCommentsId: (slug, id) => `${prefix}/recipes/${slug}/comments/${id}`,
shoppingListsId: (id) => `${prefix}/shopping-lists/${id}`, shoppingListsId: (id) => `${prefix}/shopping-lists/${id}`,
siteSettingsCustomPagesId: (id) => `${prefix}/site-settings/custom-pages/${id}`, siteSettingsCustomPagesId: (id) => `${prefix}/site-settings/custom-pages/${id}`,
tagsTag: (tag) => `${prefix}/tags/${tag}`, tagsTag: (tag) => `${prefix}/tags/${tag}`,

View File

@ -1,5 +1,6 @@
import { baseURL } from "./api-utils"; import { API_ROUTES } from "./apiRoutes";
import { apiReq } from "./api-utils"; import { apiReq } from "./api-utils";
import { baseURL } from "./api-utils";
import { store } from "../store"; import { store } from "../store";
import i18n from "@/i18n.js"; import i18n from "@/i18n.js";
@ -161,4 +162,28 @@ export const recipeAPI = {
recipeAssetPath(recipeSlug, assetName) { recipeAssetPath(recipeSlug, assetName) {
return `api/media/recipes/${recipeSlug}/assets/${assetName}`; return `api/media/recipes/${recipeSlug}/assets/${assetName}`;
}, },
/** Create comment in the Database
* @param slug
*/
async createComment(slug, data) {
const response = await apiReq.post(API_ROUTES.recipesSlugComments(slug), data);
return response.data;
},
/** Update comment in the Database
* @param slug
* @param id
*/
async updateComment(slug, id, data) {
const response = await apiReq.put(API_ROUTES.recipesSlugCommentsId(slug, id), data);
return response.data;
},
/** Delete comment from the Database
* @param slug
* @param id
*/
async deleteComment(slug, id) {
const response = await apiReq.delete(API_ROUTES.recipesSlugCommentsId(slug, id));
return response.data;
},
}; };

View File

@ -29,7 +29,7 @@
</CardImage> </CardImage>
<v-card-title class="my-n3 mb-n6"> <v-card-title class="my-n3 mb-n6">
{{ $d(new Date(planDay.date.split("-")), "short") }} {{ $d(new Date(planDay.date.replaceAll("-", "/")), "short") }}
</v-card-title> </v-card-title>
<v-card-subtitle class="mb-0 pb-0"> {{ planDay.meals[0].name }}</v-card-subtitle> <v-card-subtitle class="mb-0 pb-0"> {{ planDay.meals[0].name }}</v-card-subtitle>
<v-hover v-slot="{ hover }"> <v-hover v-slot="{ hover }">

View File

@ -140,6 +140,7 @@ export default {
dateDif() { dateDif() {
let startDate = new Date(this.startDate); let startDate = new Date(this.startDate);
let endDate = new Date(this.endDate); let endDate = new Date(this.endDate);
console.log(startDate, endDate);
let dateDif = (endDate - startDate) / (1000 * 3600 * 24) + 1; let dateDif = (endDate - startDate) / (1000 * 3600 * 24) + 1;
@ -227,6 +228,7 @@ export default {
nextEndDate.setDate(nextEndDate.getDate() + 4); nextEndDate.setDate(nextEndDate.getDate() + 4);
this.startDate = nextMonday.toISOString().substr(0, 10); this.startDate = nextMonday.toISOString().substr(0, 10);
this.endDate = nextEndDate.toISOString().substr(0, 10); this.endDate = nextEndDate.toISOString().substr(0, 10);
}, },
}, },

View File

@ -0,0 +1,121 @@
<template>
<v-card>
<v-card-title class="headline">
<v-icon large class="mr-2">
mdi-comment-text-multiple-outline
</v-icon>
Comments
</v-card-title>
<v-divider class="mx-2"></v-divider>
<v-card class="ma-2" v-for="(comment, index) in comments" :key="comment.id">
<v-list-item two-line>
<v-list-item-avatar color="accent" class="white--text">
<img :src="getProfileImage(comment.user.id)" />
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title> {{ comment.user.username }}</v-list-item-title>
<v-list-item-subtitle> {{ $d(new Date(comment.dateAdded), "short") }} </v-list-item-subtitle>
</v-list-item-content>
<v-card-actions v-if="loggedIn">
<TheButton
small
minor
v-if="!editKeys[comment.id] && (user.admin || comment.user.id === user.id)"
delete
@click="deleteComment(comment.id)"
/>
<TheButton
small
v-if="!editKeys[comment.id] && comment.user.id === user.id"
edit
@click="editComment(comment.id)"
/>
<TheButton small v-else-if="editKeys[comment.id]" update @click="updateComment(comment.id, index)" />
</v-card-actions>
</v-list-item>
<div>
<v-card-text>
{{ !editKeys[comment.id] ? comment.text : null }}
<v-textarea v-if="editKeys[comment.id]" v-model="comment.text"> </v-textarea>
</v-card-text>
</div>
</v-card>
<v-card-text v-if="loggedIn">
<v-textarea auto-grow row-height="1" outlined v-model="newComment"> </v-textarea>
<div class="d-flex">
<TheButton class="ml-auto" create @click="createNewComment"> Comment </TheButton>
</div>
</v-card-text>
</v-card>
</template>
<script>
import { api } from "@/api";
const NEW_COMMENT_EVENT = "new-comment";
const UPDATE_COMMENT_EVENT = "update-comment";
export default {
props: {
comments: {
type: Array,
},
slug: {
type: String,
},
},
data() {
return {
newComment: "",
editKeys: {},
};
},
computed: {
user() {
return this.$store.getters.getUserData;
},
loggedIn() {
return this.$store.getters.getIsLoggedIn;
},
},
watch: {
comments() {
for (const comment of this.comments) {
this.$set(this.editKeys, comment.id, false);
}
},
editKeys() {
console.log(this.editKeys);
},
},
methods: {
resetImage() {
this.hideImage == false;
},
getProfileImage(id) {
return `api/users/${id}/image`;
},
editComment(id) {
this.$set(this.editKeys, id, true);
},
async updateComment(id, index) {
this.$set(this.editKeys, id, false);
await api.recipes.updateComment(this.slug, id, this.comments[index]);
this.$emit(UPDATE_COMMENT_EVENT);
},
async createNewComment() {
console.log(this.slug);
await api.recipes.createComment(this.slug, { text: this.newComment });
this.$emit(NEW_COMMENT_EVENT);
this.newComment = "";
},
async deleteComment(id) {
await api.recipes.deleteComment(this.slug, id);
this.$emit(UPDATE_COMMENT_EVENT);
},
},
};
</script>
<style lang="scss" scoped>
</style>

View File

@ -3,7 +3,7 @@
small small
@click.prevent="toggleFavorite" @click.prevent="toggleFavorite"
v-if="isFavorite || showAlways" v-if="isFavorite || showAlways"
:color="isFavorite && buttonStyle ? 'secondary' : 'primary'" :color="buttonStyle ? 'primary' : 'secondary'"
:icon="!buttonStyle" :icon="!buttonStyle"
:fab="buttonStyle" :fab="buttonStyle"
> >

View File

@ -13,8 +13,8 @@
<v-col :sm="6" :md="6" :lg="4" :xl="3" v-for="(mealplan, i) in plannedMeals" :key="i"> <v-col :sm="6" :md="6" :lg="4" :xl="3" v-for="(mealplan, i) in plannedMeals" :key="i">
<v-card class="mt-1"> <v-card class="mt-1">
<v-card-title class="mb-0 pb-0"> <v-card-title class="mb-0 pb-0">
{{ $d(new Date(mealplan.startDate.split("-")), "short") }} - {{ $d(new Date(mealplan.startDate.replaceAll("-", "/")), "short") }} -
{{ $d(new Date(mealplan.endDate.split("-")), "short") }} {{ $d(new Date(mealplan.endDate.replaceAll("-", "/")), "short") }}
</v-card-title> </v-card-title>
<v-divider class="mx-2 pa-1"></v-divider> <v-divider class="mx-2 pa-1"></v-divider>
<v-card-actions class="mb-0 px-2 py-0"> <v-card-actions class="mb-0 px-2 py-0">
@ -22,7 +22,7 @@
<v-icon left small> <v-icon left small>
mdi-cart-check mdi-cart-check
</v-icon> </v-icon>
{{$t('shopping-list.create-shopping-list')}} {{ $t("shopping-list.create-shopping-list") }}
</v-btn> </v-btn>
<v-btn <v-btn
text text
@ -35,10 +35,12 @@
<v-icon left small> <v-icon left small>
mdi-cart-check mdi-cart-check
</v-icon> </v-icon>
{{$t('shopping-list.shopping-list')}} {{ $t("shopping-list.shopping-list") }}
</v-btn> </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<TheCopyButton color="info" :copy-text="mealPlanURL(mealplan.uid)"> {{$t('general.link-copied')}} </TheCopyButton> <TheCopyButton color="info" :copy-text="mealPlanURL(mealplan.uid)">
{{ $t("general.link-copied") }}
</TheCopyButton>
</v-card-actions> </v-card-actions>
<v-list class="mt-0 pt-0"> <v-list class="mt-0 pt-0">
@ -48,7 +50,9 @@
<v-img :src="getImage(planDay['meals'][0].slug)"></v-img> <v-img :src="getImage(planDay['meals'][0].slug)"></v-img>
</v-list-item-avatar> </v-list-item-avatar>
<v-list-item-content> <v-list-item-content>
<v-list-item-title v-html="$d(new Date(planDay.date.split('-')), 'short')"></v-list-item-title> <v-list-item-title
v-html="$d(new Date(planDay.date.replaceAll('-', '/')), 'short')"
></v-list-item-title>
<v-list-item-subtitle v-html="planDay['meals'][0].name"></v-list-item-subtitle> <v-list-item-subtitle v-html="planDay['meals'][0].name"></v-list-item-subtitle>
</v-list-item-content> </v-list-item-content>
</template> </template>

View File

@ -44,6 +44,13 @@
/> />
<RecipeEditor v-else v-model="recipeDetails" ref="recipeEditor" @upload="getImageFile" /> <RecipeEditor v-else v-model="recipeDetails" ref="recipeEditor" @upload="getImageFile" />
</v-card> </v-card>
<CommentsSection
class="mt-2"
:slug="recipeDetails.slug"
:comments="recipeDetails.comments"
@new-comment="getRecipeDetails"
@update-comment="getRecipeDetails"
/>
<PrintView :recipe="recipeDetails" /> <PrintView :recipe="recipeDetails" />
</v-container> </v-container>
</template> </template>
@ -60,6 +67,7 @@ import EditorButtonRow from "@/components/Recipe/EditorButtonRow";
import NoRecipe from "@/components/Fallbacks/NoRecipe"; import NoRecipe from "@/components/Fallbacks/NoRecipe";
import { user } from "@/mixins/user"; import { user } from "@/mixins/user";
import { router } from "@/routes"; import { router } from "@/routes";
import CommentsSection from "@/components/Recipe/CommentSection";
export default { export default {
components: { components: {
@ -71,6 +79,7 @@ export default {
PrintView, PrintView,
NoRecipe, NoRecipe,
FavoriteBadge, FavoriteBadge,
CommentsSection,
}, },
mixins: [user], mixins: [user],
inject: { inject: {

View File

@ -5,6 +5,7 @@ from mealie.db.db_base import BaseDocument
from mealie.db.models.event import Event, EventNotification from mealie.db.models.event import Event, EventNotification
from mealie.db.models.group import Group from mealie.db.models.group import Group
from mealie.db.models.mealplan import MealPlan from mealie.db.models.mealplan import MealPlan
from mealie.db.models.recipe.comment import RecipeComment
from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag
from mealie.db.models.settings import CustomPage, SiteSettings from mealie.db.models.settings import CustomPage, SiteSettings
from mealie.db.models.shopping_list import ShoppingList from mealie.db.models.shopping_list import ShoppingList
@ -12,6 +13,7 @@ from mealie.db.models.sign_up import SignUp
from mealie.db.models.theme import SiteThemeModel from mealie.db.models.theme import SiteThemeModel
from mealie.db.models.users import LongLiveToken, User from mealie.db.models.users import LongLiveToken, User
from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse
from mealie.schema.comments import CommentOut
from mealie.schema.event_notifications import EventNotificationIn from mealie.schema.event_notifications import EventNotificationIn
from mealie.schema.events import Event as EventSchema from mealie.schema.events import Event as EventSchema
from mealie.schema.meal import MealPlanOut from mealie.schema.meal import MealPlanOut
@ -110,6 +112,13 @@ class _Users(BaseDocument):
return self.schema.from_orm(entry) return self.schema.from_orm(entry)
class _Comments(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = RecipeComment
self.schema = CommentOut
class _LongLiveToken(BaseDocument): class _LongLiveToken(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "id" self.primary_key = "id"
@ -190,6 +199,7 @@ class Database:
self.events = _Events() self.events = _Events()
self.event_notifications = _EventNotification() self.event_notifications = _EventNotification()
self.shopping_lists = _ShoppingList() self.shopping_lists = _ShoppingList()
self.comments = _Comments()
db = Database() db = Database()

View File

@ -8,6 +8,7 @@ class BaseMixins:
def update(self, *args, **kwarg): def update(self, *args, **kwarg):
self.__init__(*args, **kwarg) self.__init__(*args, **kwarg)
@classmethod
def get_ref(cls_type, session: Session, match_value: str, match_attr: str = "id"): def get_ref(cls_type, session: Session, match_value: str, match_attr: str = "id"):
eff_ref = getattr(cls_type, match_attr) eff_ref = getattr(cls_type, match_attr)
return session.query(cls_type).filter(eff_ref == match_value).one_or_none() return session.query(cls_type).filter(eff_ref == match_value).one_or_none()

View File

@ -16,7 +16,6 @@ class RecipeAsset(SqlAlchemyBase):
icon=None, icon=None,
file_name=None, file_name=None,
) -> None: ) -> None:
print("Asset Saved", name)
self.name = name self.name = name
self.file_name = file_name self.file_name = file_name
self.icon = icon self.icon = icon

View File

@ -0,0 +1,36 @@
from datetime import datetime
from uuid import uuid4
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models.recipe.recipe import RecipeModel
from mealie.db.models.users import User
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, orm
def generate_uuid():
return str(uuid4())
class RecipeComment(SqlAlchemyBase, BaseMixins):
__tablename__ = "recipe_comments"
id = Column(Integer, primary_key=True)
uuid = Column(Integer, unique=True, nullable=False, default=generate_uuid)
parent_id = Column(Integer, ForeignKey("recipes.id"), nullable=False)
recipe = orm.relationship("RecipeModel", back_populates="comments")
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
user = orm.relationship("User", back_populates="comments", single_parent=True, foreign_keys=[user_id])
date_added = Column(DateTime, default=datetime.now)
text = Column(String)
def __init__(self, recipe_slug, user, text, session, date_added=None, **_) -> None:
self.text = text
self.recipe = RecipeModel.get_ref(session, recipe_slug, "slug")
self.date_added = date_added or datetime.now()
if isinstance(user, dict):
user = user.get("id")
self.user = User.get_ref(session, user)
def update(self, text, **_) -> None:
self.text = text

View File

@ -55,6 +55,8 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
collection_class=ordering_list("position"), collection_class=ordering_list("position"),
) )
comments: list = orm.relationship("RecipeComment", back_populates="recipe", cascade="all, delete, delete-orphan")
# Mealie Specific # Mealie Specific
slug = sa.Column(sa.String, index=True, unique=True) slug = sa.Column(sa.String, index=True, unique=True)
settings = orm.relationship("RecipeSettings", uselist=False, cascade="all, delete-orphan") settings = orm.relationship("RecipeSettings", uselist=False, cascade="all, delete-orphan")

View File

@ -33,6 +33,10 @@ class User(SqlAlchemyBase, BaseMixins):
LongLiveToken, back_populates="user", cascade="all, delete, delete-orphan", single_parent=True LongLiveToken, back_populates="user", cascade="all, delete, delete-orphan", single_parent=True
) )
comments: list = orm.relationship(
"RecipeComment", back_populates="user", cascade="all, delete, delete-orphan", single_parent=True
)
favorite_recipes: list[RecipeModel] = orm.relationship(RecipeModel, back_populates="favorited_by") favorite_recipes: list[RecipeModel] = orm.relationship(RecipeModel, back_populates="favorited_by")
def __init__( def __init__(
@ -56,8 +60,7 @@ class User(SqlAlchemyBase, BaseMixins):
self.password = password self.password = password
self.favorite_recipes = [ self.favorite_recipes = [
RecipeModel.get_ref(RecipeModel, session=session, match_value=x, match_attr="slug") RecipeModel.get_ref(session=session, match_value=x, match_attr="slug") for x in favorite_recipes
for x in favorite_recipes
] ]
if self.username is None: if self.username is None:
@ -78,8 +81,7 @@ class User(SqlAlchemyBase, BaseMixins):
self.password = password self.password = password
self.favorite_recipes = [ self.favorite_recipes = [
RecipeModel.get_ref(RecipeModel, session=session, match_value=x, match_attr="slug") RecipeModel.get_ref(session=session, match_value=x, match_attr="slug") for x in favorite_recipes
for x in favorite_recipes
] ]
def update_password(self, password): def update_password(self, password):

View File

@ -1,5 +1,5 @@
from fastapi import APIRouter from fastapi import APIRouter
from mealie.routes.recipe import all_recipe_routes, category_routes, recipe_crud_routes, tag_routes from mealie.routes.recipe import all_recipe_routes, category_routes, comments, recipe_crud_routes, tag_routes
recipe_router = APIRouter() recipe_router = APIRouter()
@ -7,3 +7,4 @@ recipe_router.include_router(all_recipe_routes.router)
recipe_router.include_router(recipe_crud_routes.router) recipe_router.include_router(recipe_crud_routes.router)
recipe_router.include_router(category_routes.router) recipe_router.include_router(category_routes.router)
recipe_router.include_router(tag_routes.router) recipe_router.include_router(tag_routes.router)
recipe_router.include_router(comments.router)

View File

@ -0,0 +1,54 @@
from http.client import HTTPException
from fastapi import APIRouter, Depends, status
from mealie.db.database import db
from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.comments import CommentIn, CommentOut, CommentSaveToDB
from mealie.schema.user import UserInDB
from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/api", tags=["Recipe Comments"])
@router.post("/recipes/{slug}/comments")
async def create_comment(
slug: str,
new_comment: CommentIn,
session: Session = Depends(generate_session),
current_user: UserInDB = Depends(get_current_user),
):
""" Create comment in the Database """
new_comment = CommentSaveToDB(user=current_user.id, text=new_comment.text, recipe_slug=slug)
return db.comments.create(session, new_comment)
@router.put("/recipes/{slug}/comments/{id}")
async def update_comment(
id: int,
new_comment: CommentIn,
session: Session = Depends(generate_session),
current_user: UserInDB = Depends(get_current_user),
):
""" Update comment in the Database """
old_comment: CommentOut = db.comments.get(session, id)
if current_user.id != old_comment.user.id:
raise HTTPException(status.HTTP_401_UNAUTHORIZED)
return db.comments.update(session, id, new_comment)
@router.delete("/recipes/{slug}/comments/{id}")
async def delete_comment(
id: int, session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
):
""" Delete comment from the Database """
comment: CommentOut = db.comments.get(session, id)
print(current_user.id, comment.user.id, current_user.admin)
if current_user.id == comment.user.id or current_user.admin:
db.comments.delete(session, id)
return
raise HTTPException(status.HTTP_401_UNAUTHORIZED)

44
mealie/schema/comments.py Normal file
View File

@ -0,0 +1,44 @@
from datetime import datetime
from typing import Optional
from fastapi_camelcase import CamelModel
from pydantic.utils import GetterDict
class UserBase(CamelModel):
id: int
username: Optional[str]
admin: bool
class Config:
orm_mode = True
class CommentIn(CamelModel):
text: str
class CommentSaveToDB(CommentIn):
recipe_slug: str
user: int
class Config:
orm_mode = True
class CommentOut(CommentIn):
id: int
uuid: str
recipe_slug: str
date_added: datetime
user: UserBase
class Config:
orm_mode = True
@classmethod
def getter_dict(_cls, name_orm):
return {
**GetterDict(name_orm),
"recipe_slug": name_orm.recipe.slug,
}

View File

@ -5,6 +5,7 @@ from typing import Any, Optional
from fastapi_camelcase import CamelModel from fastapi_camelcase import CamelModel
from mealie.core.config import app_dirs from mealie.core.config import app_dirs
from mealie.db.models.recipe.recipe import RecipeModel from mealie.db.models.recipe.recipe import RecipeModel
from mealie.schema.comments import CommentOut
from pydantic import BaseModel, Field, validator from pydantic import BaseModel, Field, validator
from pydantic.utils import GetterDict from pydantic.utils import GetterDict
from slugify import slugify from slugify import slugify
@ -102,6 +103,8 @@ class Recipe(RecipeSummary):
org_url: Optional[str] = Field(None, alias="orgURL") org_url: Optional[str] = Field(None, alias="orgURL")
extras: Optional[dict] = {} extras: Optional[dict] = {}
comments: Optional[list[CommentOut]] = []
@staticmethod @staticmethod
def directory_from_slug(slug) -> Path: def directory_from_slug(slug) -> Path:
return app_dirs.RECIPE_DATA_DIR.joinpath(slug) return app_dirs.RECIPE_DATA_DIR.joinpath(slug)

View File

@ -129,6 +129,9 @@ def backup_all(
db_export.export_items(all_recipes, "recipes", export_list=False, slug_folder=True) db_export.export_items(all_recipes, "recipes", export_list=False, slug_folder=True)
db_export.export_templates(all_recipes) db_export.export_templates(all_recipes)
all_comments = db.comments.get_all(session)
db_export.export_items(all_comments, "comments")
if export_settings: if export_settings:
all_settings = db.settings.get_all(session) all_settings = db.settings.get_all(session)
db_export.export_items(all_settings, "settings") db_export.export_items(all_settings, "settings")

View File

@ -6,6 +6,7 @@ from typing import Callable
from mealie.core.config import app_dirs from mealie.core.config import app_dirs
from mealie.db.database import db from mealie.db.database import db
from mealie.schema.comments import CommentOut
from mealie.schema.event_notifications import EventNotificationIn from mealie.schema.event_notifications import EventNotificationIn
from mealie.schema.recipe import Recipe from mealie.schema.recipe import Recipe
from mealie.schema.restore import ( from mealie.schema.restore import (
@ -85,6 +86,22 @@ class ImportDatabase:
return imports return imports
def import_comments(self):
comment_dir: Path = self.import_dir.joinpath("comments", "comments.json")
comments = ImportDatabase.read_models_file(file_path=comment_dir, model=CommentOut)
for comment in comments:
comment: CommentOut
self.import_model(
db_table=db.comments,
model=comment,
return_model=ThemeImport,
name_attr="uuid",
search_key="uuid",
)
@staticmethod @staticmethod
def _recipe_migration(recipe_dict: dict) -> dict: def _recipe_migration(recipe_dict: dict) -> dict:
if recipe_dict.get("categories", False): if recipe_dict.get("categories", False):
@ -364,6 +381,9 @@ def import_database(
if import_notifications: if import_notifications:
notification_report = import_session.import_notifications() notification_report = import_session.import_notifications()
if import_recipes:
import_session.import_comments()
import_session.clean_up() import_session.clean_up()
return { return {