diff --git a/frontend/src/api/category.js b/frontend/src/api/category.js index f60739c8d..b08a14e59 100644 --- a/frontend/src/api/category.js +++ b/frontend/src/api/category.js @@ -8,6 +8,7 @@ const categoryURLs = { getAll: `${prefix}`, getCategory: category => `${prefix}/${category}`, deleteCategory: category => `${prefix}/${category}`, + updateCategory: category => `${prefix}/${category}`, }; export const categoryAPI = { @@ -24,6 +25,15 @@ export const categoryAPI = { let response = await apiReq.get(categoryURLs.getCategory(category)); return response.data; }, + async update(name, newName, overrideRequest = false) { + let response = await apiReq.put(categoryURLs.updateCategory(name), { + name: newName, + }); + if (!overrideRequest) { + store.dispatch("requestCategories"); + } + return response.data; + }, async delete(category) { let response = await apiReq.delete(categoryURLs.deleteCategory(category)); store.dispatch("requestCategories"); @@ -37,6 +47,7 @@ const tagURLs = { getAll: `${tagPrefix}`, getTag: tag => `${tagPrefix}/${tag}`, deleteTag: tag => `${tagPrefix}/${tag}`, + updateTag: tag => `${tagPrefix}/${tag}`, }; export const tagAPI = { @@ -53,6 +64,15 @@ export const tagAPI = { let response = await apiReq.get(tagURLs.getTag(tag)); return response.data; }, + async update(name, newName, overrideRequest = false) { + let response = await apiReq.put(tagURLs.updateTag(name), { name: newName }); + + if (!overrideRequest) { + store.dispatch("requestTags"); + } + + return response.data; + }, async delete(tag) { let response = await apiReq.delete(tagURLs.deleteTag(tag)); store.dispatch("requestTags"); diff --git a/frontend/src/components/UI/Dialogs/BaseDialog.vue b/frontend/src/components/UI/Dialogs/BaseDialog.vue index a1f64a258..c0f9b04ec 100644 --- a/frontend/src/components/UI/Dialogs/BaseDialog.vue +++ b/frontend/src/components/UI/Dialogs/BaseDialog.vue @@ -1,21 +1,34 @@ @@ -39,6 +52,7 @@ export default { data() { return { dialog: false, + loading: false, }; }, methods: { diff --git a/frontend/src/components/UI/TheSidebar.vue b/frontend/src/components/UI/TheSidebar.vue index b9d3d6a45..d36e7066a 100644 --- a/frontend/src/components/UI/TheSidebar.vue +++ b/frontend/src/components/UI/TheSidebar.vue @@ -163,6 +163,11 @@ export default { to: "/admin/settings", title: this.$t("settings.site-settings"), }, + { + icon: "mdi-tools", + to: "/admin/toolbox", + title: this.$t("settings.toolbox.toolbox"), + }, { icon: "mdi-account-group", to: "/admin/manage-users", diff --git a/frontend/src/locales/messages/en-US.json b/frontend/src/locales/messages/en-US.json index d8d82108d..55e0ab035 100644 --- a/frontend/src/locales/messages/en-US.json +++ b/frontend/src/locales/messages/en-US.json @@ -218,6 +218,10 @@ "test-webhooks": "Test Webhooks", "the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "The URLs listed below will receive webhooks containing the recipe data for the meal plan on it's scheduled day. Currently Webhooks will execute at", "webhook-url": "Webhook URL" + }, + "toolbox": { + "toolbox": "Toolbox", + "new-name": "New Name" } }, "user": { diff --git a/frontend/src/pages/Admin/ToolBox/CategoryTagEditor/index.vue b/frontend/src/pages/Admin/ToolBox/CategoryTagEditor/index.vue new file mode 100644 index 000000000..aea670d6e --- /dev/null +++ b/frontend/src/pages/Admin/ToolBox/CategoryTagEditor/index.vue @@ -0,0 +1,169 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/pages/Admin/ToolBox/index.vue b/frontend/src/pages/Admin/ToolBox/index.vue index d7714badc..a65bc552f 100644 --- a/frontend/src/pages/Admin/ToolBox/index.vue +++ b/frontend/src/pages/Admin/ToolBox/index.vue @@ -1,15 +1,47 @@ - \ No newline at end of file diff --git a/frontend/src/routes/admin.js b/frontend/src/routes/admin.js index 5b51907f0..492f29032 100644 --- a/frontend/src/routes/admin.js +++ b/frontend/src/routes/admin.js @@ -7,9 +7,10 @@ import Profile from "@/pages/Admin/Profile"; import ManageUsers from "@/pages/Admin/ManageUsers"; import Settings from "@/pages/Admin/Settings"; import About from "@/pages/Admin/About"; +import Toolbox from "@/pages/Admin/Toolbox"; import { store } from "../store"; -export const adminRoutes = { +export const adminRoutes = { path: "/admin", component: Admin, beforeEnter: (to, _from, next) => { @@ -72,6 +73,13 @@ export const adminRoutes = { title: "settings.site-settings", }, }, + { + path: "toolbox", + component: Toolbox, + meta: { + title: "settings.toolbox.toolbox", + }, + }, { path: "about", component: About, diff --git a/mealie/db/models/recipe/category.py b/mealie/db/models/recipe/category.py index 7a69044f6..ffaa48638 100644 --- a/mealie/db/models/recipe/category.py +++ b/mealie/db/models/recipe/category.py @@ -11,28 +11,28 @@ site_settings2categories = sa.Table( "site_settings2categoories", SqlAlchemyBase.metadata, sa.Column("sidebar_id", sa.Integer, sa.ForeignKey("site_settings.id")), - sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")), + sa.Column("category_id", sa.String, sa.ForeignKey("categories.id")), ) 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")), + sa.Column("category_id", sa.String, sa.ForeignKey("categories.id")), ) recipes2categories = sa.Table( "recipes2categories", SqlAlchemyBase.metadata, sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")), - sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")), + sa.Column("category_id", sa.String, sa.ForeignKey("categories.id")), ) custom_pages2categories = sa.Table( "custom_pages2categories", SqlAlchemyBase.metadata, sa.Column("custom_page_id", sa.Integer, sa.ForeignKey("custom_pages.id")), - sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")), + sa.Column("category_id", sa.String, sa.ForeignKey("categories.id")), ) @@ -52,6 +52,9 @@ class Category(SqlAlchemyBase): self.name = name.strip() self.slug = slugify(name) + def update(self, name, session=None) -> None: + self.__init__(name, session) + @staticmethod def get_ref(session, slug: str): return session.query(Category).filter(Category.slug == slug).one() diff --git a/mealie/db/models/recipe/tag.py b/mealie/db/models/recipe/tag.py index d0cc0d39f..435f22ba4 100644 --- a/mealie/db/models/recipe/tag.py +++ b/mealie/db/models/recipe/tag.py @@ -11,7 +11,7 @@ recipes2tags = sa.Table( "recipes2tags", SqlAlchemyBase.metadata, sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")), - sa.Column("tag_slug", sa.Integer, sa.ForeignKey("tags.slug")), + sa.Column("tag_id", sa.Integer, sa.ForeignKey("tags.id")), ) @@ -31,6 +31,9 @@ class Tag(SqlAlchemyBase): self.name = name.strip() self.slug = slugify(self.name) + def update(self, name, session=None) -> None: + self.__init__(name, session) + @staticmethod def create_if_not_exist(session, name: str = None): test_slug = slugify(name) diff --git a/mealie/routes/recipe/category_routes.py b/mealie/routes/recipe/category_routes.py index 043bfca9d..7774fc295 100644 --- a/mealie/routes/recipe/category_routes.py +++ b/mealie/routes/recipe/category_routes.py @@ -33,6 +33,18 @@ def get_all_recipes_by_category(category: str, session: Session = Depends(genera return db.categories.get(session, category) +@router.put("/{category}", response_model=RecipeCategoryResponse) +async def update_recipe_category( + category: str, + new_category: CategoryIn, + session: Session = Depends(generate_session), + current_user=Depends(get_current_user), +): + """ Updates an existing Tag in the database """ + + return db.categories.update(session, category, new_category.dict()) + + @router.delete("/{category}") async def delete_recipe_category( category: str, session: Session = Depends(generate_session), current_user=Depends(get_current_user) diff --git a/mealie/routes/recipe/tag_routes.py b/mealie/routes/recipe/tag_routes.py index a38875143..fee09c78b 100644 --- a/mealie/routes/recipe/tag_routes.py +++ b/mealie/routes/recipe/tag_routes.py @@ -29,21 +29,21 @@ async def create_recipe_tag( return db.tags.create(session, tag.dict()) -@router.put("") -async def update_recipe_tag( - tag: TagIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user) -): - """ Creates a Tag in the database """ - - return "NOT IMPLEMENTED" - - @router.get("/{tag}", response_model=RecipeTagResponse) def get_all_recipes_by_tag(tag: str, session: Session = Depends(generate_session)): """ Returns a list of recipes associated with the provided tag. """ return db.tags.get(session, tag) +@router.put("/{tag}", response_model=RecipeTagResponse) +async def update_recipe_tag( + tag: str, new_tag: TagIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user) +): + """ Updates an existing Tag in the database """ + + return db.tags.update(session, tag, new_tag.dict()) + + @router.delete("/{tag}") async def delete_recipe_tag( tag: str, session: Session = Depends(generate_session), current_user=Depends(get_current_user) diff --git a/mealie/schema/category.py b/mealie/schema/category.py index 5e7f1c842..074e90cc6 100644 --- a/mealie/schema/category.py +++ b/mealie/schema/category.py @@ -2,6 +2,7 @@ from typing import List, Optional from fastapi_camelcase import CamelModel from mealie.schema.recipe import Recipe +from pydantic.utils import GetterDict class CategoryIn(CamelModel): @@ -15,6 +16,13 @@ class CategoryBase(CategoryIn): class Config: orm_mode = True + @classmethod + def getter_dict(_cls, name_orm): + return { + **GetterDict(name_orm), + "total_recipes": len(name_orm.recipes), + } + class RecipeCategoryResponse(CategoryBase): recipes: Optional[List[Recipe]]