mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-24 15:49:42 +02:00
* extended query filter to accept nested tables * decoupled timeline api from recipe slug * modified frontend to use simplified events api * fixed nested loop index ghosting * updated existing tests * gave mypy a snack * added tests for nested queries * fixed "last made" render error * decoupled recipe timeline from dialog * removed unused props * tweaked recipe get_all to accept ids * created group global timeline added new timeline page to sidebar reformatted the recipe timeline added vertical option to recipe card mobile * extracted timeline item into its own component * fixed apploader centering * added paginated scrolling to recipe timeline * added sort direction config fixed infinite scroll on dialog fixed hasMore var not resetting during instantiation * added sort direction to user preferences * updated API docs with new query filter feature * better error tracing * fix for recipe not found response * simplified recipe crud route for slug/id added test for fetching by slug/id * made query filter UUID validation clearer * moved timeline menu option below shopping lists --------- Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
116 lines
4.8 KiB
Python
116 lines
4.8 KiB
Python
from datetime import datetime, time, timedelta, timezone
|
|
|
|
from pydantic import UUID4
|
|
|
|
from mealie.db.db_setup import session_context
|
|
from mealie.repos.all_repositories import get_repositories
|
|
from mealie.schema.meal_plan.new_meal import PlanEntryType
|
|
from mealie.schema.recipe.recipe import RecipeSummary
|
|
from mealie.schema.recipe.recipe_timeline_events import RecipeTimelineEventCreate, TimelineEventType
|
|
from mealie.schema.response.pagination import PaginationQuery
|
|
from mealie.schema.user.user import DEFAULT_INTEGRATION_ID
|
|
from mealie.services.event_bus_service.event_bus_service import EventBusService
|
|
from mealie.services.event_bus_service.event_types import (
|
|
EventOperation,
|
|
EventRecipeData,
|
|
EventRecipeTimelineEventData,
|
|
EventTypes,
|
|
)
|
|
|
|
|
|
def create_mealplan_timeline_events(group_id: UUID4 | None = None):
|
|
event_time = datetime.now(timezone.utc)
|
|
|
|
with session_context() as session:
|
|
repos = get_repositories(session)
|
|
if group_id is None:
|
|
# if not specified, we check all groups
|
|
groups_data = repos.groups.page_all(PaginationQuery(page=1, per_page=-1))
|
|
group_ids = [group.id for group in groups_data.items]
|
|
|
|
else:
|
|
group_ids = [group_id]
|
|
|
|
for group_id in group_ids:
|
|
event_bus_service = EventBusService(session=session, group_id=group_id)
|
|
|
|
timeline_events_to_create: list[RecipeTimelineEventCreate] = []
|
|
recipes_to_update: dict[UUID4, RecipeSummary] = {}
|
|
recipe_id_to_slug_map: dict[UUID4, str] = {}
|
|
|
|
mealplans = repos.meals.get_today(group_id)
|
|
for mealplan in mealplans:
|
|
if not (mealplan.recipe and mealplan.user_id):
|
|
continue
|
|
|
|
user = repos.users.get_one(mealplan.user_id)
|
|
if not user:
|
|
continue
|
|
|
|
# TODO: make this translatable
|
|
if mealplan.entry_type == PlanEntryType.side:
|
|
event_subject = f"{user.full_name} made this as a side"
|
|
|
|
else:
|
|
event_subject = f"{user.full_name} made this for {mealplan.entry_type.value}"
|
|
|
|
query_start_time = datetime.combine(datetime.now(timezone.utc).date(), time.min)
|
|
query_end_time = query_start_time + timedelta(days=1)
|
|
query = PaginationQuery(
|
|
query_filter=(
|
|
f'recipe_id = "{mealplan.recipe_id}" '
|
|
f'AND timestamp >= "{query_start_time.isoformat()}" '
|
|
f'AND timestamp < "{query_end_time.isoformat()}" '
|
|
f'AND subject = "{event_subject}"'
|
|
)
|
|
)
|
|
|
|
# if this event already exists, don't create it again
|
|
events = repos.recipe_timeline_events.page_all(pagination=query)
|
|
if events.items:
|
|
continue
|
|
|
|
# bump up the last made date
|
|
last_made = mealplan.recipe.last_made
|
|
if (
|
|
not last_made or last_made.date() < event_time.date()
|
|
) and mealplan.recipe_id not in recipes_to_update:
|
|
recipes_to_update[mealplan.recipe_id] = mealplan.recipe
|
|
|
|
timeline_events_to_create.append(
|
|
RecipeTimelineEventCreate(
|
|
user_id=user.id,
|
|
subject=event_subject,
|
|
event_type=TimelineEventType.info,
|
|
timestamp=event_time,
|
|
recipe_id=mealplan.recipe_id,
|
|
)
|
|
)
|
|
|
|
recipe_id_to_slug_map[mealplan.recipe_id] = mealplan.recipe.slug
|
|
|
|
if not timeline_events_to_create:
|
|
return
|
|
|
|
# TODO: use bulk operations
|
|
for event in timeline_events_to_create:
|
|
new_event = repos.recipe_timeline_events.create(event)
|
|
event_bus_service.dispatch(
|
|
integration_id=DEFAULT_INTEGRATION_ID,
|
|
group_id=group_id,
|
|
event_type=EventTypes.recipe_updated,
|
|
document_data=EventRecipeTimelineEventData(
|
|
operation=EventOperation.create,
|
|
recipe_slug=recipe_id_to_slug_map[new_event.recipe_id],
|
|
recipe_timeline_event_id=new_event.id,
|
|
),
|
|
)
|
|
|
|
for recipe in recipes_to_update.values():
|
|
repos.recipes.patch(recipe.slug, {"last_made": event_time})
|
|
event_bus_service.dispatch(
|
|
integration_id=DEFAULT_INTEGRATION_ID,
|
|
group_id=group_id,
|
|
event_type=EventTypes.recipe_updated,
|
|
document_data=EventRecipeData(operation=EventOperation.update, recipe_slug=recipe.slug),
|
|
)
|