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"},
]