mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-05-31 04:05:33 -04:00
feat: additional events dispatch (#1999)
* formatting * add new user type * add registration event * publish events on new mealplan * update UI for supported types + type updates
This commit is contained in:
parent
c33b14e3d2
commit
fc92c39b7c
@ -3,7 +3,7 @@
|
|||||||
<BaseDialog
|
<BaseDialog
|
||||||
v-model="deleteDialog"
|
v-model="deleteDialog"
|
||||||
color="error"
|
color="error"
|
||||||
:title="$t('general.confirm')"
|
:title="$tc('general.confirm')"
|
||||||
:icon="$globals.icons.alertCircle"
|
:icon="$globals.icons.alertCircle"
|
||||||
@confirm="deleteNotifier(deleteTargetId)"
|
@confirm="deleteNotifier(deleteTargetId)"
|
||||||
>
|
>
|
||||||
@ -35,8 +35,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</BasePageTitle>
|
</BasePageTitle>
|
||||||
|
|
||||||
<BannerExperimental issue="https://github.com/hay-kot/mealie/issues/833" />
|
|
||||||
|
|
||||||
<BaseButton create @click="createDialog = true" />
|
<BaseButton create @click="createDialog = true" />
|
||||||
<v-expansion-panels v-if="notifiers" class="mt-2">
|
<v-expansion-panels v-if="notifiers" class="mt-2">
|
||||||
<v-expansion-panel v-for="(notifier, index) in notifiers" :key="index" class="my-2 left-border rounded">
|
<v-expansion-panel v-for="(notifier, index) in notifiers" :key="index" class="my-2 left-border rounded">
|
||||||
@ -59,36 +57,38 @@
|
|||||||
|
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<p class="pt-4">What events should this notifier subscribe to?</p>
|
<p class="pt-4">What events should this notifier subscribe to?</p>
|
||||||
<template v-for="(opt, idx) in optionsKeys">
|
<div class="notifier-options">
|
||||||
<v-checkbox
|
<section v-for="sec in optionsSections" :key="sec.id">
|
||||||
v-if="!opt.divider"
|
<h4>
|
||||||
:key="'option-' + idx"
|
{{ sec.text }}
|
||||||
v-model="notifiers[index].options[opt.key]"
|
</h4>
|
||||||
hide-details
|
<v-checkbox
|
||||||
dense
|
v-for="opt in sec.options"
|
||||||
:label="opt.text"
|
:key="opt.key"
|
||||||
></v-checkbox>
|
v-model="notifiers[index].options[opt.key]"
|
||||||
<div v-else :key="'divider-' + idx" class="mt-4">
|
hide-details
|
||||||
{{ opt.text }}
|
dense
|
||||||
</div>
|
:label="opt.text"
|
||||||
</template>
|
/>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
<v-card-actions class="py-0">
|
<v-card-actions class="py-0">
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<BaseButtonGroup
|
<BaseButtonGroup
|
||||||
:buttons="[
|
:buttons="[
|
||||||
{
|
{
|
||||||
icon: $globals.icons.delete,
|
icon: $globals.icons.delete,
|
||||||
text: $t('general.delete'),
|
text: $tc('general.delete'),
|
||||||
event: 'delete',
|
event: 'delete',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: $globals.icons.testTube,
|
icon: $globals.icons.testTube,
|
||||||
text: $t('general.test'),
|
text: $tc('general.test'),
|
||||||
event: 'test',
|
event: 'test',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: $globals.icons.save,
|
icon: $globals.icons.save,
|
||||||
text: $t('general.save'),
|
text: $tc('general.save'),
|
||||||
event: 'save',
|
event: 'save',
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
@ -110,12 +110,14 @@ import { GroupEventNotifierCreate, GroupEventNotifierOut } from "~/lib/api/types
|
|||||||
|
|
||||||
interface OptionKey {
|
interface OptionKey {
|
||||||
text: string;
|
text: string;
|
||||||
key: string;
|
key: keyof GroupEventNotifierOut["options"];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OptionDivider {
|
|
||||||
divider: true;
|
interface OptionSection {
|
||||||
|
id: number;
|
||||||
text: string;
|
text: string;
|
||||||
|
options: OptionKey[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -173,127 +175,125 @@ export default defineComponent({
|
|||||||
// Options Definitions
|
// Options Definitions
|
||||||
const { i18n } = useContext();
|
const { i18n } = useContext();
|
||||||
|
|
||||||
const optionsKeys: (OptionKey | OptionDivider)[] = [
|
const optionsSections: OptionSection[] = [
|
||||||
{
|
{
|
||||||
divider: true,
|
id: 1,
|
||||||
text: "Recipe Events",
|
text: "Recipe Events",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
text: i18n.t("general.create") as string,
|
||||||
|
key: "recipeCreated",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: i18n.t("general.update") as string,
|
||||||
|
key: "recipeUpdated",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: i18n.t("general.delete") as string,
|
||||||
|
key: "recipeDeleted",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.t("general.create") as string,
|
id: 2,
|
||||||
key: "recipeCreated",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: i18n.t("general.update") as string,
|
|
||||||
key: "recipeUpdated",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: i18n.t("general.delete") as string,
|
|
||||||
key: "recipeDeleted",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
divider: true,
|
|
||||||
text: "User Events",
|
text: "User Events",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
text: "When a new user joins your group",
|
||||||
|
key: "userSignup",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "When a new user joins your group",
|
id: 3,
|
||||||
key: "userSignup",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
divider: true,
|
|
||||||
text: "Data Events",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "When a new data migration is completed",
|
|
||||||
key: "dataMigrations",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "When a data export is completed",
|
|
||||||
key: "dataExport",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "When a data import is completed",
|
|
||||||
key: "dataImport",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
divider: true,
|
|
||||||
text: "Mealplan Events",
|
text: "Mealplan Events",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
text: "When a user in your group creates a new mealplan",
|
||||||
|
key: "mealplanEntryCreated",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "When a user in your group creates a new mealplan",
|
id: 4,
|
||||||
key: "mealplanEntryCreated",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
divider: true,
|
|
||||||
text: "Shopping List Events",
|
text: "Shopping List Events",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
text: i18n.t("general.create") as string,
|
||||||
|
key: "shoppingListCreated",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: i18n.t("general.update") as string,
|
||||||
|
key: "shoppingListUpdated",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: i18n.t("general.delete") as string,
|
||||||
|
key: "shoppingListDeleted",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.t("general.create") as string,
|
id: 5,
|
||||||
key: "shoppingListCreated",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: i18n.t("general.update") as string,
|
|
||||||
key: "shoppingListUpdated",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: i18n.t("general.delete") as string,
|
|
||||||
key: "shoppingListDeleted",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
divider: true,
|
|
||||||
text: "Cookbook Events",
|
text: "Cookbook Events",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
text: i18n.t("general.create") as string,
|
||||||
|
key: "cookbookCreated",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: i18n.t("general.update") as string,
|
||||||
|
key: "cookbookUpdated",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: i18n.t("general.delete") as string,
|
||||||
|
key: "cookbookDeleted",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.t("general.create") as string,
|
id: 6,
|
||||||
key: "cookbookCreated",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: i18n.t("general.update") as string,
|
|
||||||
key: "cookbookUpdated",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: i18n.t("general.delete") as string,
|
|
||||||
key: "cookbookDeleted",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
divider: true,
|
|
||||||
text: "Tag Events",
|
text: "Tag Events",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
text: i18n.t("general.create") as string,
|
||||||
|
key: "tagCreated",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: i18n.t("general.update") as string,
|
||||||
|
key: "tagUpdated",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: i18n.t("general.delete") as string,
|
||||||
|
key: "tagDeleted",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.t("general.create") as string,
|
id: 7,
|
||||||
key: "tagCreated",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: i18n.t("general.update") as string,
|
|
||||||
key: "tagUpdated",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: i18n.t("general.delete") as string,
|
|
||||||
key: "tagDeleted",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
divider: true,
|
|
||||||
text: "Category Events",
|
text: "Category Events",
|
||||||
},
|
options: [
|
||||||
{
|
{
|
||||||
text: i18n.t("general.create") as string,
|
text: i18n.t("general.create") as string,
|
||||||
key: "categoryCreated",
|
key: "categoryCreated",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.t("general.update") as string,
|
text: i18n.t("general.update") as string,
|
||||||
key: "categoryUpdated",
|
key: "categoryUpdated",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.t("general.delete") as string,
|
text: i18n.t("general.delete") as string,
|
||||||
key: "categoryDeleted",
|
key: "categoryDeleted",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
openDelete,
|
openDelete,
|
||||||
optionsKeys,
|
|
||||||
notifiers,
|
notifiers,
|
||||||
createNotifierData,
|
createNotifierData,
|
||||||
|
optionsSections,
|
||||||
deleteNotifier,
|
deleteNotifier,
|
||||||
testNotifier,
|
testNotifier,
|
||||||
saveNotifier,
|
saveNotifier,
|
||||||
@ -305,3 +305,11 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.notifier-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -5,7 +5,8 @@ from fastapi import APIRouter, Depends, HTTPException
|
|||||||
|
|
||||||
from mealie.core.exceptions import mealie_registered_exceptions
|
from mealie.core.exceptions import mealie_registered_exceptions
|
||||||
from mealie.repos.repository_meals import RepositoryMeals
|
from mealie.repos.repository_meals import RepositoryMeals
|
||||||
from mealie.routes._base import BaseUserController, controller
|
from mealie.routes._base import controller
|
||||||
|
from mealie.routes._base.base_controllers import BaseCrudController
|
||||||
from mealie.routes._base.mixins import HttpRepo
|
from mealie.routes._base.mixins import HttpRepo
|
||||||
from mealie.schema import mapper
|
from mealie.schema import mapper
|
||||||
from mealie.schema.meal_plan import CreatePlanEntry, ReadPlanEntry, SavePlanEntry, UpdatePlanEntry
|
from mealie.schema.meal_plan import CreatePlanEntry, ReadPlanEntry, SavePlanEntry, UpdatePlanEntry
|
||||||
@ -14,12 +15,13 @@ from mealie.schema.meal_plan.plan_rules import PlanRulesDay
|
|||||||
from mealie.schema.recipe.recipe import Recipe
|
from mealie.schema.recipe.recipe import Recipe
|
||||||
from mealie.schema.response.pagination import PaginationQuery
|
from mealie.schema.response.pagination import PaginationQuery
|
||||||
from mealie.schema.response.responses import ErrorResponse
|
from mealie.schema.response.responses import ErrorResponse
|
||||||
|
from mealie.services.event_bus_service.event_types import EventMealplanCreatedData, EventTypes
|
||||||
|
|
||||||
router = APIRouter(prefix="/groups/mealplans", tags=["Groups: Mealplans"])
|
router = APIRouter(prefix="/groups/mealplans", tags=["Groups: Mealplans"])
|
||||||
|
|
||||||
|
|
||||||
@controller(router)
|
@controller(router)
|
||||||
class GroupMealplanController(BaseUserController):
|
class GroupMealplanController(BaseCrudController):
|
||||||
@cached_property
|
@cached_property
|
||||||
def repo(self) -> RepositoryMeals:
|
def repo(self) -> RepositoryMeals:
|
||||||
return self.repos.meals.by_group(self.group_id)
|
return self.repos.meals.by_group(self.group_id)
|
||||||
@ -117,7 +119,21 @@ class GroupMealplanController(BaseUserController):
|
|||||||
@router.post("", response_model=ReadPlanEntry, status_code=201)
|
@router.post("", response_model=ReadPlanEntry, status_code=201)
|
||||||
def create_one(self, data: CreatePlanEntry):
|
def create_one(self, data: CreatePlanEntry):
|
||||||
data = mapper.cast(data, SavePlanEntry, group_id=self.group.id)
|
data = mapper.cast(data, SavePlanEntry, group_id=self.group.id)
|
||||||
return self.mixins.create_one(data)
|
result = self.mixins.create_one(data)
|
||||||
|
|
||||||
|
self.publish_event(
|
||||||
|
event_type=EventTypes.mealplan_entry_created,
|
||||||
|
document_data=EventMealplanCreatedData(
|
||||||
|
mealplan_id=result.id,
|
||||||
|
recipe_id=data.recipe_id,
|
||||||
|
recipe_name=result.recipe.name if result.recipe else None,
|
||||||
|
recipe_slug=result.recipe.slug if result.recipe else None,
|
||||||
|
date=data.date,
|
||||||
|
),
|
||||||
|
message=f"Mealplan entry created for {data.date} for {data.entry_type}",
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
@router.get("/{item_id}", response_model=ReadPlanEntry)
|
@router.get("/{item_id}", response_model=ReadPlanEntry)
|
||||||
def get_one(self, item_id: int):
|
def get_one(self, item_id: int):
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from fastapi import APIRouter, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
|
||||||
from mealie.core.config import get_app_settings
|
from mealie.core.config import get_app_settings
|
||||||
from mealie.repos.all_repositories import get_repositories
|
from mealie.repos.all_repositories import get_repositories
|
||||||
@ -6,6 +6,8 @@ from mealie.routes._base import BasePublicController, controller
|
|||||||
from mealie.schema.response import ErrorResponse
|
from mealie.schema.response import ErrorResponse
|
||||||
from mealie.schema.user.registration import CreateUserRegistration
|
from mealie.schema.user.registration import CreateUserRegistration
|
||||||
from mealie.schema.user.user import UserOut
|
from mealie.schema.user.user import UserOut
|
||||||
|
from mealie.services.event_bus_service.event_bus_service import EventBusService
|
||||||
|
from mealie.services.event_bus_service.event_types import EventTypes, EventUserSignupData
|
||||||
from mealie.services.user_services.registration_service import RegistrationService
|
from mealie.services.user_services.registration_service import RegistrationService
|
||||||
|
|
||||||
router = APIRouter(prefix="/register")
|
router = APIRouter(prefix="/register")
|
||||||
@ -13,6 +15,8 @@ router = APIRouter(prefix="/register")
|
|||||||
|
|
||||||
@controller(router)
|
@controller(router)
|
||||||
class RegistrationController(BasePublicController):
|
class RegistrationController(BasePublicController):
|
||||||
|
event_bus: EventBusService = Depends(EventBusService.create)
|
||||||
|
|
||||||
@router.post("", response_model=UserOut, status_code=status.HTTP_201_CREATED)
|
@router.post("", response_model=UserOut, status_code=status.HTTP_201_CREATED)
|
||||||
def register_new_user(self, data: CreateUserRegistration):
|
def register_new_user(self, data: CreateUserRegistration):
|
||||||
settings = get_app_settings()
|
settings = get_app_settings()
|
||||||
@ -29,4 +33,13 @@ class RegistrationController(BasePublicController):
|
|||||||
self.translator,
|
self.translator,
|
||||||
)
|
)
|
||||||
|
|
||||||
return registration_service.register_user(data)
|
result = registration_service.register_user(data)
|
||||||
|
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
integration_id="registration",
|
||||||
|
group_id=result.group_id,
|
||||||
|
event_type=EventTypes.user_signup,
|
||||||
|
document_data=EventUserSignupData(username=result.username, email=result.email),
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import date, datetime
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -57,6 +57,8 @@ class EventTypes(Enum):
|
|||||||
class EventDocumentType(Enum):
|
class EventDocumentType(Enum):
|
||||||
generic = "generic"
|
generic = "generic"
|
||||||
|
|
||||||
|
user = "user"
|
||||||
|
|
||||||
category = "category"
|
category = "category"
|
||||||
cookbook = "cookbook"
|
cookbook = "cookbook"
|
||||||
mealplan = "mealplan"
|
mealplan = "mealplan"
|
||||||
@ -82,6 +84,23 @@ class EventDocumentDataBase(MealieModel):
|
|||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class EventMealplanCreatedData(EventDocumentDataBase):
|
||||||
|
document_type = EventDocumentType.mealplan
|
||||||
|
operation = EventOperation.create
|
||||||
|
mealplan_id: int
|
||||||
|
date: date
|
||||||
|
recipe_id: UUID4 | None
|
||||||
|
recipe_name: str | None
|
||||||
|
recipe_slug: str | None
|
||||||
|
|
||||||
|
|
||||||
|
class EventUserSignupData(EventDocumentDataBase):
|
||||||
|
document_type = EventDocumentType.user
|
||||||
|
operation = EventOperation.create
|
||||||
|
username: str
|
||||||
|
email: str
|
||||||
|
|
||||||
|
|
||||||
class EventCategoryData(EventDocumentDataBase):
|
class EventCategoryData(EventDocumentDataBase):
|
||||||
document_type = EventDocumentType.category
|
document_type = EventDocumentType.category
|
||||||
category_id: UUID4
|
category_id: UUID4
|
||||||
|
@ -70,7 +70,6 @@ class RegistrationService:
|
|||||||
if registration.group:
|
if registration.group:
|
||||||
new_group = True
|
new_group = True
|
||||||
group = self._register_new_group()
|
group = self._register_new_group()
|
||||||
|
|
||||||
elif registration.group_token and registration.group_token != "":
|
elif registration.group_token and registration.group_token != "":
|
||||||
token_entry = self.repos.group_invite_tokens.get_one(registration.group_token)
|
token_entry = self.repos.group_invite_tokens.get_one(registration.group_token)
|
||||||
if not token_entry:
|
if not token_entry:
|
||||||
@ -88,9 +87,7 @@ class RegistrationService:
|
|||||||
user = self._create_new_user(group, new_group)
|
user = self._create_new_user(group, new_group)
|
||||||
|
|
||||||
if new_group and registration.seed_data:
|
if new_group and registration.seed_data:
|
||||||
|
|
||||||
seeder_service = SeederService(self.repos, user, group)
|
seeder_service = SeederService(self.repos, user, group)
|
||||||
|
|
||||||
seeder_service.seed_foods(registration.locale)
|
seeder_service.seed_foods(registration.locale)
|
||||||
seeder_service.seed_labels(registration.locale)
|
seeder_service.seed_labels(registration.locale)
|
||||||
seeder_service.seed_units(registration.locale)
|
seeder_service.seed_units(registration.locale)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user