diff --git a/alembic/versions/2022-11-03-13.10.24_1923519381ad_renamed_timeline_event_message_and_.py b/alembic/versions/2022-11-03-13.10.24_1923519381ad_renamed_timeline_event_message_and_.py new file mode 100644 index 000000000..761f3d7e7 --- /dev/null +++ b/alembic/versions/2022-11-03-13.10.24_1923519381ad_renamed_timeline_event_message_and_.py @@ -0,0 +1,28 @@ +"""added recipe last made timestamp + +Revision ID: 1923519381ad +Revises: 2ea7a807915c +Create Date: 2022-11-03 13:10:24.811134 + +""" +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "1923519381ad" +down_revision = "2ea7a807915c" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("recipes", sa.Column("last_made", sa.DateTime(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("recipes", "last_made") + # ### end Alembic commands ### diff --git a/frontend/components/Domain/Recipe/RecipeCardSection.vue b/frontend/components/Domain/Recipe/RecipeCardSection.vue index 64a998da9..08aa7198a 100644 --- a/frontend/components/Domain/Recipe/RecipeCardSection.vue +++ b/frontend/components/Domain/Recipe/RecipeCardSection.vue @@ -49,6 +49,12 @@ {{ $t("general.updated") }} + + + {{ $globals.icons.chefHat }} + + {{ "Last Made" }} + + diff --git a/frontend/components/Domain/Recipe/RecipeDialogShare.vue b/frontend/components/Domain/Recipe/RecipeDialogShare.vue index edc403772..5a9dfeed5 100644 --- a/frontend/components/Domain/Recipe/RecipeDialogShare.vue +++ b/frontend/components/Domain/Recipe/RecipeDialogShare.vue @@ -84,7 +84,6 @@ export default defineComponent({ return props.value; }, set: (val) => { - console.log(val); context.emit("input", val); }, }); diff --git a/frontend/components/Domain/Recipe/RecipeLastMade.vue b/frontend/components/Domain/Recipe/RecipeLastMade.vue new file mode 100644 index 000000000..948650d04 --- /dev/null +++ b/frontend/components/Domain/Recipe/RecipeLastMade.vue @@ -0,0 +1,143 @@ + + + diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageHeader.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageHeader.vue index 3d05bf88d..21f8d329e 100644 --- a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageHeader.vue +++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageHeader.vue @@ -10,6 +10,14 @@ +
+ +
import { defineComponent, useContext, computed, ref, watch, useRouter } from "@nuxtjs/composition-api"; import RecipeRating from "~/components/Domain/Recipe/RecipeRating.vue"; +import RecipeLastMade from "~/components/Domain/Recipe/RecipeLastMade.vue"; import RecipeActionMenu from "~/components/Domain/Recipe/RecipeActionMenu.vue"; import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue"; import { useStaticRoutes } from "~/composables/api"; @@ -69,6 +78,7 @@ export default defineComponent({ RecipeTimeCard, RecipeActionMenu, RecipeRating, + RecipeLastMade, }, props: { recipe: { diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageTitleContent.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageTitleContent.vue index 5ca840d86..e66b2d463 100644 --- a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageTitleContent.vue +++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageTitleContent.vue @@ -5,6 +5,14 @@ {{ recipe.name }} +
+ +
`${prefix}/recipes/${slug}/comments`, recipesSlugCommentsId: (slug: string, id: number) => `${prefix}/recipes/${slug}/comments/${id}`, + + recipesSlugTimelineEvent: (slug: string) => `${prefix}/recipes/${slug}/timeline/events`, + recipesSlugTimelineEventId: (slug: string, id: number) => `${prefix}/recipes/${slug}/timeline/events/${id}`, }; export class RecipeAPI extends BaseCRUDAPI { @@ -126,4 +130,8 @@ export class RecipeAPI extends BaseCRUDAPI { return await this.requests.post(routes.recipesCreateFromOcr, formData); } + + async createTimelineEvent(recipeSlug: string, payload: RecipeTimelineEventIn) { + return await this.requests.post(routes.recipesSlugTimelineEvent(recipeSlug), payload); + } } diff --git a/frontend/lib/icons/icons.ts b/frontend/lib/icons/icons.ts index 9e97a668e..ab819a525 100644 --- a/frontend/lib/icons/icons.ts +++ b/frontend/lib/icons/icons.ts @@ -122,6 +122,7 @@ import { mdiCursorMove, mdiText, mdiTextBoxOutline, + mdiChefHat, } from "@mdi/js"; export const icons = { @@ -157,6 +158,7 @@ export const icons = { check: mdiCheck, checkboxBlankOutline: mdiCheckboxBlankOutline, checkboxMarkedCircle: mdiCheckboxMarkedCircle, + chefHat: mdiChefHat, clipboardCheck: mdiClipboardCheck, clockOutline: mdiClockTimeFourOutline, codeBraces: mdiCodeJson, diff --git a/mealie/db/models/recipe/recipe.py b/mealie/db/models/recipe/recipe.py index 4f356a7d9..2e0018557 100644 --- a/mealie/db/models/recipe/recipe.py +++ b/mealie/db/models/recipe/recipe.py @@ -99,6 +99,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins): # Time Stamp Properties date_added = sa.Column(sa.Date, default=datetime.date.today) date_updated = sa.Column(sa.DateTime) + last_made = sa.Column(sa.DateTime) # Shopping List Refs shopping_list_refs = orm.relationship( diff --git a/mealie/schema/recipe/recipe.py b/mealie/schema/recipe/recipe.py index 835e31d5f..486001511 100644 --- a/mealie/schema/recipe/recipe.py +++ b/mealie/schema/recipe/recipe.py @@ -98,6 +98,7 @@ class RecipeSummary(MealieModel): created_at: datetime.datetime | None update_at: datetime.datetime | None + last_made: datetime.datetime | None class Config: orm_mode = True diff --git a/mealie/schema/recipe/recipe_timeline_events.py b/mealie/schema/recipe/recipe_timeline_events.py index d92e2c993..83b0cf8b2 100644 --- a/mealie/schema/recipe/recipe_timeline_events.py +++ b/mealie/schema/recipe/recipe_timeline_events.py @@ -1,7 +1,7 @@ from datetime import datetime from enum import Enum -from pydantic import UUID4 +from pydantic import UUID4, Field from mealie.schema._mealie.mealie_model import MealieModel from mealie.schema.response.pagination import PaginationBase @@ -20,7 +20,7 @@ class RecipeTimelineEventIn(MealieModel): subject: str event_type: TimelineEventType - message: str | None = None + message: str | None = Field(alias="eventMessage") image: str | None = None timestamp: datetime = datetime.now() @@ -36,7 +36,7 @@ class RecipeTimelineEventCreate(RecipeTimelineEventIn): class RecipeTimelineEventUpdate(MealieModel): subject: str - message: str | None = None + message: str | None = Field(alias="eventMessage") image: str | None = None diff --git a/tests/integration_tests/user_recipe_tests/test_recipe_timeline_events.py b/tests/integration_tests/user_recipe_tests/test_recipe_timeline_events.py index 201d50bfa..c4e1bd37d 100644 --- a/tests/integration_tests/user_recipe_tests/test_recipe_timeline_events.py +++ b/tests/integration_tests/user_recipe_tests/test_recipe_timeline_events.py @@ -176,6 +176,52 @@ def test_delete_timeline_event(api_client: TestClient, unique_user: TestUser, re assert event_response.status_code == 404 +def test_timeline_event_message_alias(api_client: TestClient, unique_user: TestUser, recipes: list[Recipe]): + # create an event using aliases + recipe = recipes[0] + new_event_data = { + "userId": unique_user.user_id, + "subject": random_string(), + "eventType": "info", + "eventMessage": random_string(), # eventMessage is the correct alias for the message + } + + event_response = api_client.post( + api_routes.recipes_slug_timeline_events(recipe.slug), + json=new_event_data, + headers=unique_user.token, + ) + new_event = RecipeTimelineEventOut.parse_obj(event_response.json()) + assert str(new_event.user_id) == new_event_data["userId"] + assert str(new_event.event_type) == new_event_data["eventType"] + assert new_event.message == new_event_data["eventMessage"] + + # fetch the new event + event_response = api_client.get( + api_routes.recipes_slug_timeline_events_item_id(recipe.slug, new_event.id), headers=unique_user.token + ) + assert event_response.status_code == 200 + + event = RecipeTimelineEventOut.parse_obj(event_response.json()) + assert event == new_event + + # update the event message + new_subject = random_string() + new_message = random_string() + updated_event_data = {"subject": new_subject, "eventMessage": new_message} + + event_response = api_client.put( + api_routes.recipes_slug_timeline_events_item_id(recipe.slug, new_event.id), + json=updated_event_data, + headers=unique_user.token, + ) + assert event_response.status_code == 200 + + updated_event = RecipeTimelineEventOut.parse_obj(event_response.json()) + assert updated_event.subject == new_subject + assert updated_event.message == new_message + + def test_create_recipe_with_timeline_event(api_client: TestClient, unique_user: TestUser, recipes: list[Recipe]): # make sure when the recipes fixture was created that all recipes have at least one event for recipe in recipes: diff --git a/tests/unit_tests/services_tests/backup_v2_tests/test_alchemy_exporter.py b/tests/unit_tests/services_tests/backup_v2_tests/test_alchemy_exporter.py index 7e8475832..929ccf9fc 100644 --- a/tests/unit_tests/services_tests/backup_v2_tests/test_alchemy_exporter.py +++ b/tests/unit_tests/services_tests/backup_v2_tests/test_alchemy_exporter.py @@ -4,7 +4,7 @@ from mealie.core.config import get_app_settings from mealie.services.backups_v2.alchemy_exporter import AlchemyExporter ALEMBIC_VERSIONS = [ - {"version_num": "2ea7a807915c"}, + {"version_num": "1923519381ad"}, ]