mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-12 21:35:18 -05:00
Feature/event notifications (#399)
* additional server events * sort 'recent recipes' by updated * remove duplicate code * fixes #396 * set color * consolidate tag/category pages * set colors * list unorganized recipes * cleanup old code * remove flash message, switch to global snackbar * cancel to close * cleanup * notifications first pass * test notification * complete notification feature * use background tasks * add url param * update documentation Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
@@ -95,6 +95,7 @@ def determine_sqlite_path(path=False, suffix=DB_VERSION) -> str:
|
||||
class AppSettings(BaseSettings):
|
||||
global DATA_DIR
|
||||
PRODUCTION: bool = Field(True, env="PRODUCTION")
|
||||
BASE_URL: str = "http://localhost:8080"
|
||||
IS_DEMO: bool = False
|
||||
API_PORT: int = 9000
|
||||
API_DOCS: bool = True
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from logging import getLogger
|
||||
|
||||
from mealie.db.db_base import BaseDocument
|
||||
from mealie.db.models.event import Event
|
||||
from mealie.db.models.event import Event, EventNotification
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.mealplan import MealPlanModel
|
||||
from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag
|
||||
@@ -10,6 +10,7 @@ from mealie.db.models.sign_up import SignUp
|
||||
from mealie.db.models.theme import SiteThemeModel
|
||||
from mealie.db.models.users import LongLiveToken, User
|
||||
from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse
|
||||
from mealie.schema.event_notifications import EventNotificationIn
|
||||
from mealie.schema.events import Event as EventSchema
|
||||
from mealie.schema.meal import MealPlanInDB
|
||||
from mealie.schema.recipe import Recipe
|
||||
@@ -156,6 +157,13 @@ class _Events(BaseDocument):
|
||||
self.schema = EventSchema
|
||||
|
||||
|
||||
class _EventNotification(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = EventNotification
|
||||
self.schema = EventNotificationIn
|
||||
|
||||
|
||||
class Database:
|
||||
def __init__(self) -> None:
|
||||
self.recipes = _Recipes()
|
||||
@@ -170,6 +178,7 @@ class Database:
|
||||
self.groups = _Groups()
|
||||
self.custom_pages = _CustomPages()
|
||||
self.events = _Events()
|
||||
self.event_notifications = _EventNotification()
|
||||
|
||||
|
||||
db = Database()
|
||||
|
||||
@@ -1,14 +1,45 @@
|
||||
import sqlalchemy as sa
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from sqlalchemy import Boolean, Column, DateTime, Integer, String
|
||||
|
||||
|
||||
class EventNotification(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "event_notifications"
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
type = Column(String)
|
||||
notification_url = Column(String)
|
||||
|
||||
# Event Types
|
||||
general = Column(Boolean, default=False)
|
||||
recipe = Column(Boolean, default=False)
|
||||
backup = Column(Boolean, default=False)
|
||||
scheduled = Column(Boolean, default=False)
|
||||
migration = Column(Boolean, default=False)
|
||||
group = Column(Boolean, default=False)
|
||||
user = Column(Boolean, default=False)
|
||||
|
||||
def __init__(
|
||||
self, name, notification_url, type, general, recipe, backup, scheduled, migration, group, user, *args, **kwargs
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.notification_url = notification_url
|
||||
self.type = type
|
||||
self.general = general
|
||||
self.recipe = recipe
|
||||
self.backup = backup
|
||||
self.scheduled = scheduled
|
||||
self.migration = migration
|
||||
self.group = group
|
||||
self.user = user
|
||||
|
||||
|
||||
class Event(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "events"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
title = sa.Column(sa.String)
|
||||
text = sa.Column(sa.String)
|
||||
time_stamp = sa.Column(sa.DateTime)
|
||||
category = sa.Column(sa.String)
|
||||
id = Column(Integer, primary_key=True)
|
||||
title = Column(String)
|
||||
text = Column(String)
|
||||
time_stamp = Column(DateTime)
|
||||
category = Column(String)
|
||||
|
||||
def __init__(self, title, text, time_stamp, category, *args, **kwargs) -> None:
|
||||
self.title = title
|
||||
|
||||
@@ -1,28 +1,97 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from http.client import HTTPException
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
from mealie.schema.events import EventsOut
|
||||
from mealie.schema.event_notifications import EventNotificationIn, EventNotificationOut
|
||||
from mealie.schema.events import EventsOut, TestEvent
|
||||
from mealie.schema.user import UserInDB
|
||||
from mealie.services.events import test_notification
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
router = APIRouter(prefix="/events", tags=["App Events"])
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@router.get("", response_model=EventsOut)
|
||||
async def get_events(session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
|
||||
async def get_events(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)):
|
||||
""" Get event from the Database """
|
||||
# Get Item
|
||||
return EventsOut(total=db.events.count_all(session), events=db.events.get_all(session, order_by="time_stamp"))
|
||||
|
||||
|
||||
@router.delete("")
|
||||
async def delete_events(session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
|
||||
async def delete_events(
|
||||
session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
|
||||
):
|
||||
""" Get event from the Database """
|
||||
# Get Item
|
||||
return db.events.delete_all(session)
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_event(id: int, session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
|
||||
async def delete_event(
|
||||
id: int, session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
|
||||
):
|
||||
""" Delete event from the Database """
|
||||
return db.events.delete(session, id)
|
||||
|
||||
|
||||
@router.post("/notifications")
|
||||
async def create_event_notification(
|
||||
event_data: EventNotificationIn,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
):
|
||||
""" Create event_notification in the Database """
|
||||
|
||||
return db.event_notifications.create(session, event_data)
|
||||
|
||||
|
||||
@router.post("/notifications/test")
|
||||
async def test_notification_route(
|
||||
test_data: TestEvent,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
):
|
||||
""" Create event_notification in the Database """
|
||||
|
||||
if test_data.id:
|
||||
event_obj: EventNotificationIn = db.event_notifications.get(session, test_data.id)
|
||||
test_data.test_url = event_obj.notification_url
|
||||
|
||||
try:
|
||||
test_notification(test_data.test_url)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
@router.get("/notifications", response_model=list[EventNotificationOut])
|
||||
async def get_all_event_notification(
|
||||
session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
|
||||
):
|
||||
""" Get all event_notification from the Database """
|
||||
# Get Item
|
||||
return db.event_notifications.get_all(session, override_schema=EventNotificationOut)
|
||||
|
||||
|
||||
@router.put("/notifications/{id}")
|
||||
async def update_event_notification(
|
||||
id: int, session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
|
||||
):
|
||||
""" Update event_notification in the Database """
|
||||
# Update Item
|
||||
return {"details": "not yet implemented"}
|
||||
|
||||
|
||||
@router.delete("/notifications/{id}")
|
||||
async def delete_event_notification(
|
||||
id: int, session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
|
||||
):
|
||||
""" Delete event_notification from the Database """
|
||||
# Delete Item
|
||||
return db.event_notifications.delete(session, id)
|
||||
|
||||
@@ -2,7 +2,7 @@ import operator
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, File, HTTPException, UploadFile, status
|
||||
from mealie.core.config import app_dirs
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.core.security import create_file_token
|
||||
@@ -33,7 +33,7 @@ def available_imports():
|
||||
|
||||
|
||||
@router.post("/export/database", status_code=status.HTTP_201_CREATED)
|
||||
def export_database(data: BackupJob, session: Session = Depends(generate_session)):
|
||||
def export_database(background_tasks: BackgroundTasks, data: BackupJob, session: Session = Depends(generate_session)):
|
||||
"""Generates a backup of the recipe database in json format."""
|
||||
try:
|
||||
export_path = backup_all(
|
||||
@@ -47,7 +47,9 @@ def export_database(data: BackupJob, session: Session = Depends(generate_session
|
||||
export_users=data.options.users,
|
||||
export_groups=data.options.groups,
|
||||
)
|
||||
create_backup_event("Database Backup", f"Manual Backup Created '{Path(export_path).name}'", session)
|
||||
background_tasks.add_task(
|
||||
create_backup_event, "Database Backup", f"Manual Backup Created '{Path(export_path).name}'", session
|
||||
)
|
||||
return {"export_path": export_path}
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
@@ -75,7 +77,12 @@ async def download_backup_file(file_name: str):
|
||||
|
||||
|
||||
@router.post("/{file_name}/import", status_code=status.HTTP_200_OK)
|
||||
def import_database(file_name: str, import_data: ImportJob, session: Session = Depends(generate_session)):
|
||||
def import_database(
|
||||
background_tasks: BackgroundTasks,
|
||||
file_name: str,
|
||||
import_data: ImportJob,
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Import a database backup file generated from Mealie. """
|
||||
|
||||
db_import = imports.import_database(
|
||||
@@ -90,7 +97,7 @@ def import_database(file_name: str, import_data: ImportJob, session: Session = D
|
||||
force_import=import_data.force,
|
||||
rebase=import_data.rebase,
|
||||
)
|
||||
create_backup_event("Database Restore", f"Restore File: {file_name}", session)
|
||||
background_tasks.add_task(create_backup_event, "Database Restore", f"Restore File: {file_name}", session)
|
||||
return db_import
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
@@ -32,6 +32,7 @@ async def get_current_user_group(
|
||||
|
||||
@router.post("", status_code=status.HTTP_201_CREATED)
|
||||
async def create_group(
|
||||
background_tasks: BackgroundTasks,
|
||||
group_data: GroupBase,
|
||||
current_user=Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
@@ -40,7 +41,7 @@ async def create_group(
|
||||
|
||||
try:
|
||||
db.groups.create(session, group_data.dict())
|
||||
create_group_event("Group Created", f"'{group_data.name}' created")
|
||||
background_tasks.add_task(create_group_event, "Group Created", f"'{group_data.name}' created", session)
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@@ -58,7 +59,10 @@ async def update_group_data(
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_user_group(
|
||||
id: int, current_user=Depends(get_current_user), session: Session = Depends(generate_session)
|
||||
background_tasks: BackgroundTasks,
|
||||
id: int,
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Removes a user group from the database """
|
||||
|
||||
@@ -73,5 +77,8 @@ async def delete_user_group(
|
||||
if group.users != []:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="GROUP_WITH_USERS")
|
||||
|
||||
create_group_event("Group Deleted", f"'{group.name}' Deleted")
|
||||
background_tasks.add_task(
|
||||
create_group_event, "Group Deleted", f"'{group.name}' deleted by {current_user.full_name}", session
|
||||
)
|
||||
|
||||
db.groups.delete(session, id)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
@@ -25,16 +25,22 @@ def get_all_meals(
|
||||
|
||||
@router.post("/create", status_code=status.HTTP_201_CREATED)
|
||||
def create_meal_plan(
|
||||
data: MealPlanIn, session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
|
||||
background_tasks: BackgroundTasks,
|
||||
data: MealPlanIn,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
):
|
||||
""" Creates a meal plan database entry """
|
||||
processed_plan = process_meals(session, data)
|
||||
create_group_event("Meal Plan Created", f"Mealplan Created for '{current_user.group}'")
|
||||
background_tasks.add_task(
|
||||
create_group_event, "Meal Plan Created", f"Mealplan Created for '{current_user.group}'", session=session
|
||||
)
|
||||
return db.meals.create(session, processed_plan.dict())
|
||||
|
||||
|
||||
@router.put("/{plan_id}")
|
||||
def update_meal_plan(
|
||||
background_tasks: BackgroundTasks,
|
||||
plan_id: str,
|
||||
meal_plan: MealPlanIn,
|
||||
session: Session = Depends(generate_session),
|
||||
@@ -45,13 +51,16 @@ def update_meal_plan(
|
||||
processed_plan = MealPlanInDB(uid=plan_id, **processed_plan.dict())
|
||||
try:
|
||||
db.meals.update(session, plan_id, processed_plan.dict())
|
||||
create_group_event("Meal Plan Updated", f"Mealplan Updated for '{current_user.group}'")
|
||||
background_tasks.add_task(
|
||||
create_group_event, "Meal Plan Updated", f"Mealplan Updated for '{current_user.group}'", session=session
|
||||
)
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@router.delete("/{plan_id}")
|
||||
def delete_meal_plan(
|
||||
background_tasks: BackgroundTasks,
|
||||
plan_id,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
@@ -60,7 +69,9 @@ def delete_meal_plan(
|
||||
|
||||
try:
|
||||
db.meals.delete(session, plan_id)
|
||||
create_group_event("Meal Plan Deleted", f"Mealplan Deleted for '{current_user.group}'")
|
||||
background_tasks.add_task(
|
||||
create_group_event, "Meal Plan Deleted", f"Mealplan Deleted for '{current_user.group}'", session=session
|
||||
)
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
from shutil import copyfileobj
|
||||
|
||||
from fastapi import APIRouter, Depends, File, Form, HTTPException, status
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, File, Form, HTTPException, status
|
||||
from fastapi.datastructures import UploadFile
|
||||
from mealie.core.config import settings
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
from mealie.schema.recipe import Recipe, RecipeAsset, RecipeURLIn
|
||||
from mealie.schema.user import UserInDB
|
||||
from mealie.services.events import create_recipe_event
|
||||
from mealie.services.image.image import scrape_image, write_image
|
||||
from mealie.services.recipe.media import check_assets, delete_assets
|
||||
@@ -20,6 +22,7 @@ logger = get_logger()
|
||||
|
||||
@router.post("/create", status_code=201, response_model=str)
|
||||
def create_from_json(
|
||||
background_tasks: BackgroundTasks,
|
||||
data: Recipe,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
@@ -27,22 +30,36 @@ def create_from_json(
|
||||
""" Takes in a JSON string and loads data into the database as a new entry"""
|
||||
recipe: Recipe = db.recipes.create(session, data.dict())
|
||||
|
||||
create_recipe_event("Recipe Created", f"Recipe '{recipe.name}' created", session=session)
|
||||
background_tasks.add_task(
|
||||
create_recipe_event,
|
||||
"Recipe Created (URL)",
|
||||
f"'{recipe.name}' by {current_user.full_name} \n {settings.BASE_URL}/recipe/{recipe.slug}",
|
||||
session=session,
|
||||
attachment=recipe.image_dir.joinpath("min-original.webp"),
|
||||
)
|
||||
|
||||
return recipe.slug
|
||||
|
||||
|
||||
@router.post("/create-url", status_code=201, response_model=str)
|
||||
def parse_recipe_url(
|
||||
background_tasks: BackgroundTasks,
|
||||
url: RecipeURLIn,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
):
|
||||
""" Takes in a URL and attempts to scrape data and load it into the database """
|
||||
|
||||
recipe = create_from_url(url.url)
|
||||
recipe: Recipe = db.recipes.create(session, recipe.dict())
|
||||
create_recipe_event("Recipe Created (URL)", f"'{recipe.name}' by {current_user.full_name}", session=session)
|
||||
|
||||
background_tasks.add_task(
|
||||
create_recipe_event,
|
||||
"Recipe Created (URL)",
|
||||
f"'{recipe.name}' by {current_user.full_name} \n {settings.BASE_URL}/recipe/{recipe.slug}",
|
||||
session=session,
|
||||
attachment=recipe.image_dir.joinpath("min-original.webp"),
|
||||
)
|
||||
|
||||
return recipe.slug
|
||||
|
||||
@@ -64,7 +81,6 @@ def update_recipe(
|
||||
""" Updates a recipe by existing slug and data. """
|
||||
|
||||
recipe: Recipe = db.recipes.update(session, recipe_slug, data.dict())
|
||||
print(recipe.assets)
|
||||
|
||||
check_assets(original_slug=recipe_slug, recipe=recipe)
|
||||
|
||||
@@ -91,6 +107,7 @@ def patch_recipe(
|
||||
|
||||
@router.delete("/{recipe_slug}")
|
||||
def delete_recipe(
|
||||
background_tasks: BackgroundTasks,
|
||||
recipe_slug: str,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
@@ -100,7 +117,12 @@ def delete_recipe(
|
||||
try:
|
||||
recipe: Recipe = db.recipes.delete(session, recipe_slug)
|
||||
delete_assets(recipe_slug=recipe_slug)
|
||||
create_recipe_event("Recipe Deleted", f"'{recipe.name}' deleted by {current_user.full_name}", session=session)
|
||||
background_tasks.add_task(
|
||||
create_recipe_event,
|
||||
"Recipe Deleted",
|
||||
f"'{recipe.name}' deleted by {current_user.full_name}",
|
||||
session=session,
|
||||
)
|
||||
return recipe
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from fastapi import APIRouter, Depends, Request, status
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, Request, status
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from mealie.core import security
|
||||
@@ -15,6 +15,7 @@ router = APIRouter(prefix="/api/auth", tags=["Authentication"])
|
||||
@router.post("/token/long")
|
||||
@router.post("/token")
|
||||
def get_token(
|
||||
background_tasks: BackgroundTasks,
|
||||
request: Request,
|
||||
data: OAuth2PasswordRequestForm = Depends(),
|
||||
session: Session = Depends(generate_session),
|
||||
@@ -25,7 +26,9 @@ def get_token(
|
||||
user = authenticate_user(session, email, password)
|
||||
|
||||
if not user:
|
||||
create_user_event("Failed Login", f"Username: {email}, Source IP: '{request.client.host}'")
|
||||
background_tasks.add_task(
|
||||
create_user_event, "Failed Login", f"Username: {email}, Source IP: '{request.client.host}'"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import shutil
|
||||
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, File, HTTPException, UploadFile, status
|
||||
from fastapi.responses import FileResponse
|
||||
from mealie.core import security
|
||||
from mealie.core.config import app_dirs, settings
|
||||
@@ -17,13 +17,16 @@ router = APIRouter(prefix="/api/users", tags=["Users"])
|
||||
|
||||
@router.post("", response_model=UserOut, status_code=201)
|
||||
async def create_user(
|
||||
background_tasks: BackgroundTasks,
|
||||
new_user: UserIn,
|
||||
current_user=Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
|
||||
new_user.password = get_password_hash(new_user.password)
|
||||
create_user_event("User Created", f"Created by {current_user.full_name}", session=session)
|
||||
background_tasks.add_task(
|
||||
create_user_event, "User Created", f"Created by {current_user.full_name}", session=session
|
||||
)
|
||||
return db.users.create(session, new_user.dict())
|
||||
|
||||
|
||||
@@ -138,6 +141,7 @@ async def update_password(
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_user(
|
||||
background_tasks: BackgroundTasks,
|
||||
id: int,
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
@@ -150,6 +154,6 @@ async def delete_user(
|
||||
if current_user.id == id or current_user.admin:
|
||||
try:
|
||||
db.users.delete(session, id)
|
||||
create_user_event("User Deleted", f"User ID: {id}", session=session)
|
||||
background_tasks.add_task(create_user_event, "User Deleted", f"User ID: {id}", session=session)
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import uuid
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
|
||||
from mealie.core.security import get_password_hash
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
@@ -25,6 +25,7 @@ async def get_all_open_sign_ups(
|
||||
|
||||
@router.post("", response_model=SignUpToken)
|
||||
async def create_user_sign_up_key(
|
||||
background_tasks: BackgroundTasks,
|
||||
key_data: SignUpIn,
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
@@ -39,12 +40,16 @@ async def create_user_sign_up_key(
|
||||
"name": key_data.name,
|
||||
"admin": key_data.admin,
|
||||
}
|
||||
create_user_event("Sign-up Token Created", f"Created by {current_user.full_name}", session=session)
|
||||
|
||||
background_tasks.add_task(
|
||||
create_user_event, "Sign-up Token Created", f"Created by {current_user.full_name}", session=session
|
||||
)
|
||||
return db.sign_ups.create(session, sign_up)
|
||||
|
||||
|
||||
@router.post("/{token}")
|
||||
async def create_user_with_token(
|
||||
background_tasks: BackgroundTasks,
|
||||
token: str,
|
||||
new_user: UserIn,
|
||||
session: Session = Depends(generate_session),
|
||||
@@ -62,7 +67,9 @@ async def create_user_with_token(
|
||||
db.users.create(session, new_user.dict())
|
||||
|
||||
# DeleteToken
|
||||
create_user_event("Sign-up Token Used", f"New User {new_user.full_name}", session=session)
|
||||
background_tasks.add_task(
|
||||
create_user_event, "Sign-up Token Used", f"New User {new_user.full_name}", session=session
|
||||
)
|
||||
db.sign_ups.delete(session, token)
|
||||
|
||||
|
||||
|
||||
61
mealie/schema/event_notifications.py
Normal file
61
mealie/schema/event_notifications.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
|
||||
|
||||
class DeclaredTypes(str, Enum):
|
||||
general = "General"
|
||||
discord = "Discord"
|
||||
gotify = "Gotify"
|
||||
pushover = "Pushover"
|
||||
home_assistant = "Home Assistant"
|
||||
|
||||
|
||||
class EventNotificationOut(CamelModel):
|
||||
id: Optional[int]
|
||||
name: str = ""
|
||||
type: DeclaredTypes = DeclaredTypes.general
|
||||
general: bool = True
|
||||
recipe: bool = True
|
||||
backup: bool = True
|
||||
scheduled: bool = True
|
||||
migration: bool = True
|
||||
group: bool = True
|
||||
user: bool = True
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class EventNotificationIn(EventNotificationOut):
|
||||
notification_url: str = ""
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class Discord(CamelModel):
|
||||
webhook_id: str
|
||||
webhook_token: str
|
||||
|
||||
@property
|
||||
def create_url(self) -> str:
|
||||
return f"discord://{self.webhook_id}/{self.webhook_token}/"
|
||||
|
||||
|
||||
class GotifyPriority(str, Enum):
|
||||
low = "low"
|
||||
moderate = "moderate"
|
||||
normal = "normal"
|
||||
high = "high"
|
||||
|
||||
|
||||
class Gotify(CamelModel):
|
||||
hostname: str
|
||||
token: str
|
||||
priority: GotifyPriority = GotifyPriority.normal
|
||||
|
||||
@property
|
||||
def create_url(self) -> str:
|
||||
return f"gotifys://{self.hostname}/{self.token}/?priority={self.priority}"
|
||||
@@ -30,3 +30,8 @@ class Event(CamelModel):
|
||||
class EventsOut(CamelModel):
|
||||
total: int
|
||||
events: list[Event]
|
||||
|
||||
|
||||
class TestEvent(CamelModel):
|
||||
id: Optional[int]
|
||||
test_url: Optional[str]
|
||||
|
||||
@@ -1,23 +1,60 @@
|
||||
import apprise
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session
|
||||
from mealie.schema.events import Event, EventCategory
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
||||
def save_event(title, text, category, session: Session):
|
||||
def test_notification(notification_url, event=None) -> bool:
|
||||
|
||||
if event is None:
|
||||
event = Event(
|
||||
title="Test Notification",
|
||||
text="This is a test message from the Mealie API server",
|
||||
category=EventCategory.general.value,
|
||||
)
|
||||
|
||||
post_notifications(event, [notification_url], hard_fail=True)
|
||||
|
||||
|
||||
def post_notifications(event: Event, notification_urls=list[str], hard_fail=False, attachment=None):
|
||||
asset = apprise.AppriseAsset(async_mode=False)
|
||||
apobj = apprise.Apprise(asset=asset)
|
||||
|
||||
for dest in notification_urls:
|
||||
status = apobj.add(dest)
|
||||
|
||||
if not status and hard_fail:
|
||||
raise Exception("Apprise URL Add Failed")
|
||||
|
||||
print(attachment)
|
||||
|
||||
apobj.notify(
|
||||
body=event.text,
|
||||
title=event.title,
|
||||
attach=str(attachment),
|
||||
)
|
||||
|
||||
|
||||
def save_event(title, text, category, session: Session, attachment=None):
|
||||
event = Event(title=title, text=text, category=category)
|
||||
session = session or create_session()
|
||||
db.events.create(session, event.dict())
|
||||
|
||||
notification_objects = db.event_notifications.get(session=session, match_value=True, match_key=category, limit=9999)
|
||||
notification_urls = [x.notification_url for x in notification_objects]
|
||||
post_notifications(event, notification_urls, attachment=attachment)
|
||||
|
||||
|
||||
def create_general_event(title, text, session=None):
|
||||
category = EventCategory.general
|
||||
save_event(title=title, text=text, category=category, session=session)
|
||||
|
||||
|
||||
def create_recipe_event(title, text, session=None):
|
||||
def create_recipe_event(title, text, session=None, attachment=None):
|
||||
category = EventCategory.recipe
|
||||
save_event(title=title, text=text, category=category, session=session)
|
||||
|
||||
save_event(title=title, text=text, category=category, session=session, attachment=attachment)
|
||||
|
||||
|
||||
def create_backup_event(title, text, session=None):
|
||||
|
||||
Reference in New Issue
Block a user