add server side events

This commit is contained in:
hay-kot 2021-05-03 14:57:21 -08:00
commit 832e95f27e
26 changed files with 393 additions and 34 deletions

59
frontend/src/api/about.js Normal file
View file

@ -0,0 +1,59 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
const prefix = baseURL + "about";
const aboutURLs = {
version: `${prefix}/version`,
debug: `${prefix}`,
lastRecipe: `${prefix}/last-recipe-json`,
demo: `${prefix}/is-demo`,
log: num => `${prefix}/log/${num}`,
statistics: `${prefix}/statistics`,
events: `${prefix}/events`,
event: id => `${prefix}/events/${id}`,
};
export const aboutAPI = {
async getEvents() {
const resposne = await apiReq.get(aboutURLs.events);
return resposne.data;
},
async deleteEvent(id) {
const resposne = await apiReq.delete(aboutURLs.event(id));
return resposne.data;
},
async deleteAllEvents() {
const resposne = await apiReq.delete(aboutURLs.events);
return resposne.data;
},
// async getAppInfo() {
// const response = await apiReq.get(aboutURLs.version);
// return response.data;
// },
// async getDebugInfo() {
// const response = await apiReq.get(aboutURLs.debug);
// return response.data;
// },
// async getLogText(num) {
// const response = await apiReq.get(aboutURLs.log(num));
// return response.data;
// },
// async getLastJson() {
// const response = await apiReq.get(aboutURLs.lastRecipe);
// return response.data;
// },
// async getIsDemo() {
// const response = await apiReq.get(aboutURLs.demo);
// return response.data;
// },
// async getStatistics() {
// const response = await apiReq.get(aboutURLs.statistics);
// return response.data;
// },
};

View file

@ -11,6 +11,7 @@ import { userAPI } from "./users";
import { signupAPI } from "./signUps"; import { signupAPI } from "./signUps";
import { groupAPI } from "./groups"; import { groupAPI } from "./groups";
import { siteSettingsAPI } from "./siteSettings"; import { siteSettingsAPI } from "./siteSettings";
import { aboutAPI } from "./about";
/** /**
* The main object namespace for interacting with the backend database * The main object namespace for interacting with the backend database
@ -30,4 +31,5 @@ export const api = {
users: userAPI, users: userAPI,
signUps: signupAPI, signUps: signupAPI,
groups: groupAPI, groups: groupAPI,
about: aboutAPI,
}; };

View file

@ -9,5 +9,14 @@
"day": "numeric", "day": "numeric",
"weekday": "long", "weekday": "long",
"year": "numeric" "year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
} }
} }

View file

@ -9,5 +9,14 @@
"day": "numeric", "day": "numeric",
"weekday": "long", "weekday": "long",
"year": "numeric" "year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
} }
} }

View file

@ -19,7 +19,7 @@
<div class="text-truncate"> <div class="text-truncate">
<strong>{{ backup.name }}</strong> <strong>{{ backup.name }}</strong>
</div> </div>
<div class="text-truncate">{{ $d(new Date(backup.date), "medium") }}</div> <div class="text-truncate">{{ $d(Date.parse(backup.date), "medium") }}</div>
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>

View file

@ -15,7 +15,7 @@
</v-toolbar-items> </v-toolbar-items>
</v-toolbar> </v-toolbar>
<v-card-title> {{ name }} </v-card-title> <v-card-title> {{ name }} </v-card-title>
<v-card-subtitle class="mb-n3"> {{ $d(new Date(date), "medium") }} </v-card-subtitle> <v-card-subtitle class="mb-n3" v-if="date"> {{ $d(new Date(date), "medium") }} </v-card-subtitle>
<v-divider></v-divider> <v-divider></v-divider>
<v-card-text> <v-card-text>

View file

@ -0,0 +1,107 @@
<template>
<div>
<StatCard icon="mdi-bell-ring">
<template v-slot:after-heading>
<div class="ml-auto text-right">
<div class="body-3 grey--text font-weight-light" v-text="'Events'" />
<h3 class="display-2 font-weight-light text--primary">
<small> {{ total }}</small>
</h3>
</div>
</template>
<div class="d-flex row py-3 justify-end">
<v-btn class="mx-2" small color="primary" @click="deleteAll">
<v-icon left> mdi-notification-clear-all </v-icon> Clear
</v-btn>
</div>
<template v-slot:bottom>
<v-list subheader two-line>
<v-list-item v-for="(event, index) in events" :key="index">
<v-list-item-avatar>
<v-icon large dark :color="icons[event.category].color">
{{ icons[event.category].icon }}
</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="event.title"></v-list-item-title>
<v-list-item-subtitle v-text="event.text"></v-list-item-subtitle>
<v-list-item-subtitle>
{{ $d(Date.parse(event.timeStamp), "long") }}
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action class="ml-auto">
<v-btn large icon @click="deleteEvent(event.id)">
<v-icon color="error">mdi-delete</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</v-list>
</template>
</StatCard>
</div>
</template>
<script>
import { api } from "@/api";
import StatCard from "./StatCard";
export default {
components: { StatCard },
data() {
return {
total: 0,
events: [],
icons: {
general: {
icon: "mdi-information",
color: "info",
},
recipe: {
icon: "mdi-silverware-fork-knife",
color: "primary",
},
backup: {
icon: "mdi-backup-restore",
color: "primary",
},
schedule: {
icon: "mdi-calendar-clock",
color: "primary",
},
migration: {
icon: "mdi-database-import",
color: "primary",
},
signup: {
icon: "mdi-account",
color: "primary",
},
},
};
},
mounted() {
this.getEvents();
},
methods: {
async getEvents() {
const events = await api.about.getEvents();
this.events = events.events;
this.total = events.total;
},
async deleteEvent(id) {
await api.about.deleteEvent(id);
this.getEvents();
},
async deleteAll() {
await api.about.deleteAllEvents();
this.getEvents();
},
},
};
</script>
<style lang="scss" scoped>
</style>

View file

@ -2,14 +2,11 @@ w<template>
<v-card v-bind="$attrs" :class="classes" class="v-card--material pa-3"> <v-card v-bind="$attrs" :class="classes" class="v-card--material pa-3">
<div class="d-flex grow flex-wrap"> <div class="d-flex grow flex-wrap">
<v-sheet <v-sheet
:class="{
'pa-7': !$slots.image,
}"
:color="color" :color="color"
:max-height="icon ? 90 : undefined" :max-height="icon ? 90 : undefined"
:width="icon ? 'auto' : '100%'" :width="icon ? 'auto' : '100%'"
elevation="6" elevation="6"
class="text-start v-card--material__heading mb-n6" class="text-start v-card--material__heading mb-n6 mt-n10 pa-7"
dark dark
> >
<v-icon v-if="icon" size="32" v-text="icon" /> <v-icon v-if="icon" size="32" v-text="icon" />
@ -30,6 +27,14 @@ w<template>
<slot name="actions" /> <slot name="actions" />
</v-card-actions> </v-card-actions>
</template> </template>
<template v-if="$slots.bottom">
<v-divider class="mt-2" />
<div class="pb-0">
<slot name="bottom" />
</div>
</template>
</v-card> </v-card>
</template> </template>
@ -71,10 +76,10 @@ export default {
}; };
}, },
hasHeading() { hasHeading() {
return Boolean(this.$slots.heading || this.title || this.icon); return false;
}, },
hasAltHeading() { hasAltHeading() {
return Boolean(this.$slots.heading || (this.title && this.icon)); return false;
}, },
}, },
}; };
@ -82,14 +87,14 @@ export default {
<style lang="sass"> <style lang="sass">
.v-card--material .v-card--material
&__avatar &__avatar
position: relative position: relative
top: -64px top: -64px
margin-bottom: -32px margin-bottom: -32px
&__heading &__heading
position: relative position: relative
top: -40px top: -40px
transition: .3s ease transition: .3s ease
z-index: 1 z-index: 1
</style> </style>

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="mt-15"> <div class="mt-10">
<v-row> <v-row>
<v-col> <v-col cols="12" sm="12" md="4">
<StatCard icon="mdi-silverware-fork-knife"> <StatCard icon="mdi-silverware-fork-knife">
<template v-slot:after-heading> <template v-slot:after-heading>
<div class="ml-auto text-right"> <div class="ml-auto text-right">
@ -24,7 +24,7 @@
</template> </template>
</StatCard> </StatCard>
</v-col> </v-col>
<v-col> <v-col cols="12" sm="12" md="4">
<StatCard icon="mdi-account"> <StatCard icon="mdi-account">
<template v-slot:after-heading> <template v-slot:after-heading>
<div class="ml-auto text-right"> <div class="ml-auto text-right">
@ -45,7 +45,7 @@
</template> </template>
</StatCard> </StatCard>
</v-col> </v-col>
<v-col> <v-col cols="12" sm="12" md="4">
<StatCard icon="mdi-account-group"> <StatCard icon="mdi-account-group">
<template v-slot:after-heading> <template v-slot:after-heading>
<div class="ml-auto text-right"> <div class="ml-auto text-right">
@ -67,14 +67,21 @@
</StatCard> </StatCard>
</v-col> </v-col>
</v-row> </v-row>
<v-row class="mt-10">
<v-col cols="12" sm="12" lg="6">
<EventViewer />
</v-col>
<v-col cols="12" sm="12" lg="6"> </v-col>
</v-row>
</div> </div>
</template> </template>
<script> <script>
import { api } from "@/api"; import { api } from "@/api";
import StatCard from "./StatCard"; import StatCard from "./StatCard";
import EventViewer from "./EventViewer";
export default { export default {
components: { StatCard }, components: { StatCard, EventViewer },
data() { data() {
return { return {
statistics: { statistics: {
@ -92,7 +99,6 @@ export default {
methods: { methods: {
async getStatistics() { async getStatistics() {
this.statistics = await api.meta.getStatistics(); this.statistics = await api.meta.getStatistics();
console.log(this.statistics);
}, },
}, },
}; };

View file

@ -160,10 +160,10 @@ export default {
methods: { methods: {
updateClipboard(newClip) { updateClipboard(newClip) {
navigator.clipboard.writeText(newClip).then( navigator.clipboard.writeText(newClip).then(
function() { () => {
console.log("Copied", newClip); console.log("Copied", newClip);
}, },
function() { () => {
console.log("Copy Failed", newClip); console.log("Copy Failed", newClip);
} }
); );

View file

@ -37,7 +37,6 @@ export default {
return this.$store.getters.getSiteSettings; return this.$store.getters.getSiteSettings;
}, },
recentRecipes() { recentRecipes() {
console.log("Recent Recipes");
return this.$store.getters.getRecentRecipes; return this.$store.getters.getRecentRecipes;
}, },
}, },

View file

@ -4,11 +4,13 @@ from fastapi import FastAPI
from mealie.core import root_logger from mealie.core import root_logger
from mealie.core.config import APP_VERSION, settings from mealie.core.config import APP_VERSION, settings
from mealie.routes import backup_routes, debug_routes, migration_routes, theme_routes, utility_routes from mealie.routes import backup_routes, debug_routes, migration_routes, theme_routes, utility_routes
from mealie.routes.about import about_router
from mealie.routes.groups import groups from mealie.routes.groups import groups
from mealie.routes.mealplans import mealplans from mealie.routes.mealplans import mealplans
from mealie.routes.recipe import router as recipe_router from mealie.routes.recipe import router as recipe_router
from mealie.routes.site_settings import all_settings from mealie.routes.site_settings import all_settings
from mealie.routes.users import users from mealie.routes.users import users
from mealie.services.events import create_general_event
logger = root_logger.get_logger() logger = root_logger.get_logger()
@ -31,6 +33,7 @@ def api_routers():
app.include_router(groups.router) app.include_router(groups.router)
# Recipes # Recipes
app.include_router(recipe_router) app.include_router(recipe_router)
app.include_router(about_router)
# Meal Routes # Meal Routes
app.include_router(mealplans.router) app.include_router(mealplans.router)
# Settings Routes # Settings Routes
@ -53,6 +56,7 @@ def system_startup():
logger.info("-----SYSTEM STARTUP----- \n") logger.info("-----SYSTEM STARTUP----- \n")
logger.info("------APP SETTINGS------") logger.info("------APP SETTINGS------")
logger.info(settings.json(indent=4, exclude={"SECRET", "DEFAULT_PASSWORD", "SFTP_PASSWORD", "SFTP_USERNAME"})) logger.info(settings.json(indent=4, exclude={"SECRET", "DEFAULT_PASSWORD", "SFTP_PASSWORD", "SFTP_USERNAME"}))
create_general_event("Application Startup", f"Mealie API started on port {settings.API_PORT}")
def main(): def main():

View file

@ -1,6 +1,7 @@
from logging import getLogger from logging import getLogger
from mealie.db.db_base import BaseDocument from mealie.db.db_base import BaseDocument
from mealie.db.models.event import Event
from mealie.db.models.group import Group from mealie.db.models.group import Group
from mealie.db.models.mealplan import MealPlanModel from mealie.db.models.mealplan import MealPlanModel
from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag
@ -9,6 +10,7 @@ from mealie.db.models.sign_up import SignUp
from mealie.db.models.theme import SiteThemeModel from mealie.db.models.theme import SiteThemeModel
from mealie.db.models.users import User from mealie.db.models.users import User
from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse
from mealie.schema.events import Event as EventSchema
from mealie.schema.meal import MealPlanInDB from mealie.schema.meal import MealPlanInDB
from mealie.schema.recipe import Recipe from mealie.schema.recipe import Recipe
from mealie.schema.settings import CustomPageOut from mealie.schema.settings import CustomPageOut
@ -35,10 +37,10 @@ class _Recipes(BaseDocument):
return f"{slug}.{extension}" return f"{slug}.{extension}"
def count_uncategorized(self, session: Session) -> int: def count_uncategorized(self, session: Session) -> int:
return session.query(self.sql_model).filter(RecipeModel.recipe_category == None).count() return session.query(self.sql_model).filter(RecipeModel.recipe_category == None).count() # noqa: 711
def count_untagged(self, session: Session) -> int: def count_untagged(self, session: Session) -> int:
return session.query(self.sql_model).filter(RecipeModel.tags == None).count() return session.query(self.sql_model).filter(RecipeModel.tags == None).count() # noqa: 711
class _Categories(BaseDocument): class _Categories(BaseDocument):
@ -115,8 +117,6 @@ class _Groups(BaseDocument):
""" """
group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none() group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none()
# Potentially not needed? column is sorted by SqlAlchemy based on startDate
# return sorted(group.mealplans, key=lambda mealplan: mealplan.startDate)
return group.mealplans return group.mealplans
@ -134,6 +134,13 @@ class _CustomPages(BaseDocument):
self.schema = CustomPageOut self.schema = CustomPageOut
class _Events(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = Event
self.schema = EventSchema
class Database: class Database:
def __init__(self) -> None: def __init__(self) -> None:
self.recipes = _Recipes() self.recipes = _Recipes()
@ -146,6 +153,7 @@ class Database:
self.sign_ups = _SignUps() self.sign_ups = _SignUps()
self.groups = _Groups() self.groups = _Groups()
self.custom_pages = _CustomPages() self.custom_pages = _CustomPages()
self.events = _Events()
db = Database() db = Database()

View file

@ -23,6 +23,14 @@ class BaseDocument:
) -> List[dict]: ) -> List[dict]:
eff_schema = override_schema or self.schema eff_schema = override_schema or self.schema
if order_by:
order_attr = getattr(self.sql_model, str(order_by))
return [
eff_schema.from_orm(x)
for x in session.query(self.sql_model).order_by(order_attr.desc()).offset(start).limit(limit).all()
]
return [eff_schema.from_orm(x) for x in session.query(self.sql_model).offset(start).limit(limit).all()] return [eff_schema.from_orm(x) for x in session.query(self.sql_model).offset(start).limit(limit).all()]
def get_all_limit_columns(self, session: Session, fields: List[str], limit: int = None) -> List[SqlAlchemyBase]: def get_all_limit_columns(self, session: Session, fields: List[str], limit: int = None) -> List[SqlAlchemyBase]:
@ -155,6 +163,10 @@ class BaseDocument:
return results_as_model return results_as_model
def delete_all(self, session: Session) -> None:
session.query(self.sql_model).delete()
session.commit()
def count_all(self, session: Session, match_key=None, match_value=None) -> int: def count_all(self, session: Session, match_key=None, match_value=None) -> int:
if None in [match_key, match_value]: if None in [match_key, match_value]:

View file

@ -5,6 +5,7 @@ from mealie.db.database import db
from mealie.db.db_setup import create_session from mealie.db.db_setup import create_session
from mealie.schema.settings import SiteSettings from mealie.schema.settings import SiteSettings
from mealie.schema.theme import SiteTheme from mealie.schema.theme import SiteTheme
from mealie.services.events import create_general_event
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
logger = root_logger.get_logger("init_db") logger = root_logger.get_logger("init_db")
@ -58,6 +59,7 @@ def main():
else: else:
print("Database Doesn't Exists, Initializing...") print("Database Doesn't Exists, Initializing...")
init_db() init_db()
create_general_event("Initialize Database", "Initialize database with default values", session)
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -1,7 +1,8 @@
from mealie.db.models.event import *
from mealie.db.models.group import *
from mealie.db.models.mealplan import * from mealie.db.models.mealplan import *
from mealie.db.models.recipe.recipe import * from mealie.db.models.recipe.recipe import *
from mealie.db.models.settings import * from mealie.db.models.settings import *
from mealie.db.models.sign_up import *
from mealie.db.models.theme import * from mealie.db.models.theme import *
from mealie.db.models.users import * from mealie.db.models.users import *
from mealie.db.models.sign_up import *
from mealie.db.models.group import *

17
mealie/db/models/event.py Normal file
View file

@ -0,0 +1,17 @@
import sqlalchemy as sa
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
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)
def __init__(self, title, text, time_stamp, category, *args, **kwargs) -> None:
self.title = title
self.text = text
self.time_stamp = time_stamp
self.category = category

View file

@ -4,5 +4,5 @@ SqlAlchemyBase = dec.declarative_base()
class BaseMixins: class BaseMixins:
def _pass_on_me(): def update(self, *args, **kwarg):
pass self.__init__(*args, **kwarg)

View file

@ -0,0 +1,7 @@
from fastapi import APIRouter
from .events import router as events_router
about_router = APIRouter(prefix="/api/about")
about_router.include_router(events_router)

View file

@ -0,0 +1,29 @@
from fastapi import APIRouter, Depends
from mealie.db.database import db
from mealie.db.db_setup import generate_session
from mealie.db.models.event import Event
from mealie.routes.deps import get_current_user
from mealie.schema.events import EventsOut
from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/events", tags=["App Events"])
@router.get("", response_model=EventsOut)
async def get_events(session: Session = Depends(generate_session), current_user=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 get_events(session: Session = Depends(generate_session), current_user=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)):
""" Delete event from the Database """
return db.events.delete(session, id)

View file

@ -1,5 +1,6 @@
import operator import operator
import shutil import shutil
from pathlib import Path
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from mealie.core.config import app_dirs from mealie.core.config import app_dirs
@ -10,6 +11,7 @@ from mealie.routes.deps import get_current_user
from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup
from mealie.services.backups import imports from mealie.services.backups import imports
from mealie.services.backups.exports import backup_all from mealie.services.backups.exports import backup_all
from mealie.services.events import create_backup_event
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/api/backups", tags=["Backups"], dependencies=[Depends(get_current_user)]) router = APIRouter(prefix="/api/backups", tags=["Backups"], dependencies=[Depends(get_current_user)])
@ -45,6 +47,7 @@ def export_database(data: BackupJob, session: Session = Depends(generate_session
export_users=data.options.users, export_users=data.options.users,
export_groups=data.options.groups, export_groups=data.options.groups,
) )
create_backup_event("Manual Backup", f"Manual Backup Created '{Path(export_path).name}'", session)
return {"export_path": export_path} return {"export_path": export_path}
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
@ -75,7 +78,7 @@ async def download_backup_file(file_name: str):
def import_database(file_name: str, import_data: ImportJob, session: Session = Depends(generate_session)): def import_database(file_name: str, import_data: ImportJob, session: Session = Depends(generate_session)):
""" Import a database backup file generated from Mealie. """ """ Import a database backup file generated from Mealie. """
return imports.import_database( db_import = imports.import_database(
session=session, session=session,
archive=import_data.name, archive=import_data.name,
import_recipes=import_data.recipes, import_recipes=import_data.recipes,
@ -87,6 +90,8 @@ def import_database(file_name: str, import_data: ImportJob, session: Session = D
force_import=import_data.force, force_import=import_data.force,
rebase=import_data.rebase, rebase=import_data.rebase,
) )
create_backup_event("Database Restore", f"Restored Database File {file_name}", session)
return db_import
@router.delete("/{file_name}/delete", status_code=status.HTTP_200_OK) @router.delete("/{file_name}/delete", status_code=status.HTTP_200_OK)

View file

@ -2,6 +2,7 @@ from pathlib import Path
from fastapi_camelcase import CamelModel from fastapi_camelcase import CamelModel
class AppStatistics(CamelModel): class AppStatistics(CamelModel):
total_recipes: int total_recipes: int
total_users: int total_users: int

31
mealie/schema/events.py Normal file
View file

@ -0,0 +1,31 @@
from datetime import datetime
from enum import Enum
from typing import Optional
from fastapi_camelcase import CamelModel
from pydantic import Field
class EventCategory(str, Enum):
general = "general"
recipe = "recipe"
backup = "backup"
scheduled = "scheduled"
migration = "migration"
sign_up = "signup"
class Event(CamelModel):
id: Optional[int]
title: str
text: str
time_stamp: datetime = Field(default_factory=datetime.now)
category: EventCategory = EventCategory.general
class Config:
orm_mode = True
class EventsOut(CamelModel):
total: int
events: list[Event]

View file

@ -9,6 +9,7 @@ from mealie.core import root_logger
from mealie.core.config import app_dirs from mealie.core.config import app_dirs
from mealie.db.database import db from mealie.db.database import db
from mealie.db.db_setup import create_session from mealie.db.db_setup import create_session
from mealie.services.events import create_backup_event
from pathvalidate import sanitize_filename from pathvalidate import sanitize_filename
from pydantic.main import BaseModel from pydantic.main import BaseModel
@ -149,3 +150,5 @@ def auto_backup_job():
session = create_session() session = create_session()
backup_all(session=session, tag="Auto", templates=templates) backup_all(session=session, tag="Auto", templates=templates)
logger.info("Auto Backup Called") logger.info("Auto Backup Called")
create_backup_event("Automated Backup", "Automated backup created", session)
session.close()

40
mealie/services/events.py Normal file
View file

@ -0,0 +1,40 @@
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):
event = Event(title=title, text=text, category=category)
session = session or create_session()
db.events.create(session, event.dict())
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):
category = EventCategory.recipe
save_event(title=title, text=text, category=category, session=session)
def create_backup_event(title, text, session=None):
category = EventCategory.backup
save_event(title=title, text=text, category=category, session=session)
def create_scheduled_event(title, text, session=None):
category = EventCategory.scheduled
save_event(title=title, text=text, category=category, session=session)
def create_migration_event(title, text, session=None):
category = EventCategory.migration
save_event(title=title, text=text, category=category, session=session)
def create_sign_up_event(title, text, session=None):
category = EventCategory.sign_up
save_event(title=title, text=text, category=category, session=session)

View file

@ -2,6 +2,7 @@ import requests
from mealie.db.database import db from mealie.db.database import db
from mealie.db.db_setup import create_session from mealie.db.db_setup import create_session
from mealie.schema.user import GroupInDB from mealie.schema.user import GroupInDB
from mealie.services.events import create_scheduled_event
from mealie.services.meal_services import get_todays_meal from mealie.services.meal_services import get_todays_meal
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
@ -21,4 +22,6 @@ def post_webhooks(group: int, session: Session = None):
for url in group_settings.webhook_urls: for url in group_settings.webhook_urls:
requests.post(url, json=todays_recipe.json()) requests.post(url, json=todays_recipe.json())
create_scheduled_event("Meal Plan Webhook", f"Meal plan webhook executed for group '{group}'")
session.close() session.close()