mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 14:33:33 -07:00
notifications first pass
This commit is contained in:
parent
f45107b4f2
commit
566e947ffc
12 changed files with 571 additions and 76 deletions
BIN
frontend/public/static/gotify.png
Normal file
BIN
frontend/public/static/gotify.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
|
@ -12,6 +12,9 @@ const aboutURLs = {
|
|||
statistics: `${prefix}/statistics`,
|
||||
events: `${prefix}/events`,
|
||||
event: id => `${prefix}/events/${id}`,
|
||||
|
||||
allNotifications: `${prefix}/events/notifications`,
|
||||
notification: id => `${prefix}/events/notifications/${id}`,
|
||||
};
|
||||
|
||||
export const aboutAPI = {
|
||||
|
@ -27,6 +30,21 @@ export const aboutAPI = {
|
|||
const resposne = await apiReq.delete(aboutURLs.events);
|
||||
return resposne.data;
|
||||
},
|
||||
|
||||
async allEventNotifications() {
|
||||
const response = await apiReq.get(aboutURLs.allNotifications);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async createNotification(data) {
|
||||
const response = await apiReq.post(aboutURLs.allNotifications, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async deleteNotification(id) {
|
||||
const response = await apiReq.delete(aboutURLs.notification(id));
|
||||
return response.data;
|
||||
},
|
||||
// async getAppInfo() {
|
||||
// const response = await apiReq.get(aboutURLs.version);
|
||||
// return response.data;
|
||||
|
|
198
frontend/src/pages/Admin/ToolBox/EventNotification/index.vue
Normal file
198
frontend/src/pages/Admin/ToolBox/EventNotification/index.vue
Normal file
|
@ -0,0 +1,198 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-card outlined class="mt-n1">
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<BaseDialog title-icon="mdi-bell-alert" @submit="createNotification" title="New Notification">
|
||||
<template v-slot:open="{ open }">
|
||||
<v-btn small color="info" @click="open">
|
||||
<v-icon left>
|
||||
mdi-plus
|
||||
</v-icon>
|
||||
Notification
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card-text class="mt-2">
|
||||
We use the <a href="https://github.com/caronc/apprise/wiki" target="_blanks"> Apprise </a> library to
|
||||
generate notifications. They offer many options for services to use. Refer to their wiki for a comprehensive
|
||||
guide on how to create the URL for your service. If available, selecting the type of your notification can
|
||||
include extra features Here are some common choices.
|
||||
|
||||
<div class="d-flex justify-space-around mt-1">
|
||||
<a href="https://github.com/caronc/apprise/wiki/Notify_gotify" target="_blanks"> Gotify </a>
|
||||
<a href="https://github.com/caronc/apprise/wiki/Notify_discord" target="_blanks"> Discord </a>
|
||||
<a href="https://github.com/caronc/apprise/wiki/Notify_homeassistant" target="_blanks">
|
||||
Home Assistant
|
||||
</a>
|
||||
<a href="https://github.com/caronc/apprise/wiki/Notify_matrix" target="_blanks"> Matrix </a>
|
||||
<a href="https://github.com/caronc/apprise/wiki/Notify_pushover" target="_blanks"> Pushover </a>
|
||||
</div>
|
||||
|
||||
<v-form>
|
||||
<v-select label="Type" :items="notificationTypes" item-value="text" v-model="newNotification.type">
|
||||
</v-select>
|
||||
<v-text-field label="Name" v-model="newNotification.name"> </v-text-field>
|
||||
<v-text-field label="Notification URL" v-model="newNotification.notificationUrl"> </v-text-field>
|
||||
<v-subheader class="pa-0 mb-0">
|
||||
Select the events you would like to recieve notifications for on this URL
|
||||
</v-subheader>
|
||||
<div class="px-3 dflex row justify-space-between mt-3">
|
||||
<v-switch
|
||||
class="ma-0 py-0"
|
||||
v-for="(item, key, index) in newNotificationOptions"
|
||||
:key="index"
|
||||
v-model="newNotificationOptions[key]"
|
||||
:label="key"
|
||||
>
|
||||
</v-switch>
|
||||
</div>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</BaseDialog>
|
||||
</v-card-actions>
|
||||
<v-card-text>
|
||||
<v-simple-table>
|
||||
<template v-slot:default>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">
|
||||
Type
|
||||
</th>
|
||||
<th class="text-left">
|
||||
Name
|
||||
</th>
|
||||
<th class="text-left">
|
||||
General
|
||||
</th>
|
||||
<th class="text-left">
|
||||
Recipe
|
||||
</th>
|
||||
<th class="text-left">
|
||||
Backup
|
||||
</th>
|
||||
<th class="text-left">
|
||||
Scheduled
|
||||
</th>
|
||||
<th class="text-left">
|
||||
Migration
|
||||
</th>
|
||||
<th class="text-left">
|
||||
Group
|
||||
</th>
|
||||
<th class="text-left">
|
||||
User
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in notifications" :key="index">
|
||||
<td>
|
||||
<v-avatar size="35" class="ma-1" color="primary">
|
||||
<v-icon dark v-if="getIcon(item.type).icon"> {{ getIcon(item.type).icon }}</v-icon>
|
||||
<v-img v-else :src="getIcon(item.type).image"> </v-img>
|
||||
</v-avatar>
|
||||
{{ item.type }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.name }}
|
||||
</td>
|
||||
<td>
|
||||
<v-icon color="success"> {{ item.general ? "mdi-check" : "" }} </v-icon>
|
||||
</td>
|
||||
<td>
|
||||
<v-icon color="success"> {{ item.recipe ? "mdi-check" : "" }} </v-icon>
|
||||
</td>
|
||||
<td>
|
||||
<v-icon color="success"> {{ item.backup ? "mdi-check" : "" }} </v-icon>
|
||||
</td>
|
||||
<td>
|
||||
<v-icon color="success"> {{ item.scheduled ? "mdi-check" : "" }} </v-icon>
|
||||
</td>
|
||||
<td>
|
||||
<v-icon color="success"> {{ item.migration ? "mdi-check" : "" }} </v-icon>
|
||||
</td>
|
||||
<td>
|
||||
<v-icon color="success"> {{ item.group ? "mdi-check" : "" }} </v-icon>
|
||||
</td>
|
||||
<td>
|
||||
<v-icon color="success"> {{ item.user ? "mdi-check" : "" }} </v-icon>
|
||||
</td>
|
||||
<td>
|
||||
<v-btn small icon color="error" @click="deleteNotification(item.id)">
|
||||
<v-icon> mdi-delete </v-icon>
|
||||
</v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</template>
|
||||
</v-simple-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
|
||||
import { api } from "@/api";
|
||||
export default {
|
||||
components: {
|
||||
BaseDialog,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
notifications: [],
|
||||
newNotification: {
|
||||
type: "General",
|
||||
name: "",
|
||||
notificationUrl: "",
|
||||
},
|
||||
newNotificationOptions: {
|
||||
general: true,
|
||||
recipe: true,
|
||||
backup: true,
|
||||
scheduled: true,
|
||||
migration: true,
|
||||
group: true,
|
||||
user: true,
|
||||
},
|
||||
notificationTypes: [
|
||||
{
|
||||
text: "General",
|
||||
icon: "mdi-webhook",
|
||||
},
|
||||
{
|
||||
text: "Discord",
|
||||
icon: "mdi-discord",
|
||||
},
|
||||
{
|
||||
text: "Gotify",
|
||||
image: "./static/gotify.png",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getAllNotifications();
|
||||
},
|
||||
methods: {
|
||||
getIcon(textValue) {
|
||||
return this.notificationTypes.find(x => x.text === textValue);
|
||||
},
|
||||
|
||||
async getAllNotifications() {
|
||||
this.notifications = await api.about.allEventNotifications();
|
||||
},
|
||||
async createNotification() {
|
||||
await api.about.createNotification({ ...this.newNotification, ...this.newNotificationOptions });
|
||||
this.getAllNotifications();
|
||||
},
|
||||
async deleteNotification(id) {
|
||||
await api.about.deleteNotification(id);
|
||||
this.getAllNotifications();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
|
@ -4,6 +4,10 @@
|
|||
<v-tabs v-model="tab" background-color="primary" centered dark icons-and-text>
|
||||
<v-tabs-slider></v-tabs-slider>
|
||||
|
||||
<v-tab href="#event-notifications">
|
||||
Notify
|
||||
<v-icon>mdi-bell-alert</v-icon>
|
||||
</v-tab>
|
||||
<v-tab href="#category-editor">
|
||||
{{ $t("recipe.categories") }}
|
||||
<v-icon>mdi-tag-multiple-outline</v-icon>
|
||||
|
@ -20,20 +24,23 @@
|
|||
</v-tabs>
|
||||
|
||||
<v-tabs-items v-model="tab">
|
||||
<v-tab-item value="event-notifications"> <EventNotification /></v-tab-item>
|
||||
<v-tab-item value="category-editor"> <CategoryTagEditor :is-tags="false"/></v-tab-item>
|
||||
<v-tab-item value="tag-editor"> <CategoryTagEditor :is-tags="true" /> </v-tab-item>
|
||||
<v-tab-item value="organize"> <RecipeOrganizer :is-tags="true" /> </v-tab-item>
|
||||
<v-tab-item value="organize"> <RecipeOrganizer /> </v-tab-item>
|
||||
</v-tabs-items>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EventNotification from "./EventNotification";
|
||||
import CategoryTagEditor from "./CategoryTagEditor";
|
||||
import RecipeOrganizer from "./RecipeOrganizer";
|
||||
export default {
|
||||
components: {
|
||||
CategoryTagEditor,
|
||||
EventNotification,
|
||||
RecipeOrganizer,
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,27 +2,71 @@ from fastapi import APIRouter, Depends
|
|||
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.event_notifications import EventNotificationIn, EventNotificationOut
|
||||
from mealie.schema.events import EventsOut
|
||||
from mealie.schema.user import UserInDB
|
||||
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)):
|
||||
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.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)
|
||||
|
|
59
mealie/schema/event_notifications.py
Normal file
59
mealie/schema/event_notifications.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
|
||||
|
||||
class DeclaredTypes(str, Enum):
|
||||
general = "General"
|
||||
discord = "Discord"
|
||||
gotify = "Gotify"
|
||||
|
||||
|
||||
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}"
|
32
mealie/services/event_notifications.py
Normal file
32
mealie/services/event_notifications.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
import apprise
|
||||
from mealie.schema.event_notifications import EventNotificationIn
|
||||
from mealie.schema.events import Event
|
||||
|
||||
|
||||
def post_notifications(event: Event, notification_dests=list[EventNotificationIn]):
|
||||
# Create an Apprise instance
|
||||
|
||||
# discord = Discord(
|
||||
# webhook_id="840358511842295829",
|
||||
# webhook_token="EtcH6ACFM-qpHRkPw1TUTc_r8AiVMlKYhGEzlANvXj7SlGGFZt18dkYy96ayZHZ8HaI9",
|
||||
# )
|
||||
# Create our asset object
|
||||
asset = apprise.AppriseAsset(async_mode=False)
|
||||
|
||||
# Create our object
|
||||
apobj = apprise.Apprise(asset=asset)
|
||||
|
||||
# Add all of the notification services by their server url.
|
||||
# A sample email notification:
|
||||
|
||||
for dest in notification_dests:
|
||||
dest: EventNotificationIn
|
||||
apobj.add(dest.notification_url)
|
||||
|
||||
# A sample pushbullet notification
|
||||
# Then notify these services any time you desire. The below would
|
||||
# notify all of the services loaded into our Apprise object.
|
||||
apobj.notify(
|
||||
body=event.text,
|
||||
title=event.title,
|
||||
)
|
|
@ -1,6 +1,7 @@
|
|||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session
|
||||
from mealie.schema.events import Event, EventCategory
|
||||
from mealie.services.event_notifications import post_notifications
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
||||
|
@ -9,6 +10,9 @@ def save_event(title, text, category, session: Session):
|
|||
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)
|
||||
post_notifications(event, notification_objects)
|
||||
|
||||
|
||||
def create_general_event(title, text, session=None):
|
||||
category = EventCategory.general
|
||||
|
@ -17,6 +21,7 @@ def create_general_event(title, text, session=None):
|
|||
|
||||
def create_recipe_event(title, text, session=None):
|
||||
category = EventCategory.recipe
|
||||
|
||||
save_event(title=title, text=text, category=category, session=session)
|
||||
|
||||
|
||||
|
|
95
poetry.lock
generated
95
poetry.lock
generated
|
@ -22,6 +22,23 @@ category = "main"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "apprise"
|
||||
version = "0.9.2"
|
||||
description = "Push Notifications that work with just about every platform!"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=5.0"
|
||||
cryptography = "*"
|
||||
markdown = "*"
|
||||
PyYAML = "*"
|
||||
requests = "*"
|
||||
requests-oauthlib = "*"
|
||||
six = "*"
|
||||
|
||||
[[package]]
|
||||
name = "apscheduler"
|
||||
version = "3.7.0"
|
||||
|
@ -189,6 +206,25 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
|||
[package.extras]
|
||||
toml = ["toml"]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "3.4.7"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
cffi = ">=1.12"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
|
||||
docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
|
||||
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
|
||||
sdist = ["setuptools-rust (>=0.11.4)"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "decorator"
|
||||
version = "5.0.7"
|
||||
|
@ -464,7 +500,7 @@ source = ["Cython (>=0.29.7)"]
|
|||
name = "markdown"
|
||||
version = "3.3.4"
|
||||
description = "Python implementation of Markdown."
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
|
@ -573,6 +609,19 @@ plot = ["matplotlib"]
|
|||
tgrep = ["pyparsing"]
|
||||
twitter = ["twython"]
|
||||
|
||||
[[package]]
|
||||
name = "oauthlib"
|
||||
version = "3.1.0"
|
||||
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[package.extras]
|
||||
rsa = ["cryptography"]
|
||||
signals = ["blinker"]
|
||||
signedtoken = ["cryptography", "pyjwt (>=1.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "20.9"
|
||||
|
@ -916,6 +965,21 @@ urllib3 = ">=1.21.1,<1.27"
|
|||
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
|
||||
|
||||
[[package]]
|
||||
name = "requests-oauthlib"
|
||||
version = "1.3.0"
|
||||
description = "OAuthlib authentication support for Requests."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[package.dependencies]
|
||||
oauthlib = ">=3.0.0"
|
||||
requests = ">=2.0.0"
|
||||
|
||||
[package.extras]
|
||||
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "4.7.2"
|
||||
|
@ -1172,7 +1236,7 @@ python-versions = "*"
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "bfdb4d3d5d69e53f16b315f993b712a703058d3f59e24644681ccc9062cf5143"
|
||||
content-hash = "73bac73c62e64c90a29816dde9ef1d896e8ca0b4271e67cde6ca8cc56bd87efd"
|
||||
|
||||
[metadata.files]
|
||||
aiofiles = [
|
||||
|
@ -1187,6 +1251,10 @@ appdirs = [
|
|||
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
|
||||
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
|
||||
]
|
||||
apprise = [
|
||||
{file = "apprise-0.9.2-py2.py3-none-any.whl", hash = "sha256:e3f34fcf7cd717704f04b194f6fd62719e563b7d338415467e0cd0fbd7fc5e61"},
|
||||
{file = "apprise-0.9.2.tar.gz", hash = "sha256:263bd6ed81cb33f3d24c9353506921129890a654965fbe9014f7f562bf2ba9f9"},
|
||||
]
|
||||
apscheduler = [
|
||||
{file = "APScheduler-3.7.0-py2.py3-none-any.whl", hash = "sha256:c06cc796d5bb9eb3c4f77727f6223476eb67749e7eea074d1587550702a7fbe3"},
|
||||
{file = "APScheduler-3.7.0.tar.gz", hash = "sha256:1cab7f2521e107d07127b042155b632b7a1cd5e02c34be5a28ff62f77c900c6a"},
|
||||
|
@ -1329,6 +1397,20 @@ coverage = [
|
|||
{file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"},
|
||||
{file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"},
|
||||
]
|
||||
cryptography = [
|
||||
{file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"},
|
||||
{file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"},
|
||||
{file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"},
|
||||
{file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"},
|
||||
{file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"},
|
||||
{file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"},
|
||||
]
|
||||
decorator = [
|
||||
{file = "decorator-5.0.7-py3-none-any.whl", hash = "sha256:945d84890bb20cc4a2f4a31fc4311c0c473af65ea318617f13a7257c9a58bc98"},
|
||||
{file = "decorator-5.0.7.tar.gz", hash = "sha256:6f201a6c4dac3d187352661f508b9364ec8091217442c9478f1f83c003a0f060"},
|
||||
|
@ -1617,6 +1699,10 @@ nltk = [
|
|||
{file = "nltk-3.6.2-py3-none-any.whl", hash = "sha256:240e23ab1ab159ef9940777d30c7c72d7e76d91877099218a7585370c11f6b9e"},
|
||||
{file = "nltk-3.6.2.zip", hash = "sha256:57d556abed621ab9be225cc6d2df1edce17572efb67a3d754630c9f8381503eb"},
|
||||
]
|
||||
oauthlib = [
|
||||
{file = "oauthlib-3.1.0-py2.py3-none-any.whl", hash = "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"},
|
||||
{file = "oauthlib-3.1.0.tar.gz", hash = "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
|
||||
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
|
||||
|
@ -1854,6 +1940,11 @@ requests = [
|
|||
{file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
|
||||
{file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
|
||||
]
|
||||
requests-oauthlib = [
|
||||
{file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"},
|
||||
{file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"},
|
||||
{file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"},
|
||||
]
|
||||
rsa = [
|
||||
{file = "rsa-4.7.2-py3-none-any.whl", hash = "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2"},
|
||||
{file = "rsa-4.7.2.tar.gz", hash = "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9"},
|
||||
|
|
127
pyproject.toml
127
pyproject.toml
|
@ -1,64 +1,65 @@
|
|||
[tool.poetry]
|
||||
name = "mealie"
|
||||
version = "0.5.0b"
|
||||
description = "A Recipe Manager"
|
||||
authors = ["Hayden <hay-kot@pm.me>"]
|
||||
license = "MIT"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
start = "mealie.app:main"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
aiofiles = "0.5.0"
|
||||
aniso8601 = "7.0.0"
|
||||
appdirs = "1.4.4"
|
||||
fastapi = "^0.63.0"
|
||||
uvicorn = {extras = ["standard"], version = "^0.13.0"}
|
||||
APScheduler = "^3.6.3"
|
||||
SQLAlchemy = "^1.3.22"
|
||||
Jinja2 = "^2.11.2"
|
||||
python-dotenv = "^0.15.0"
|
||||
python-slugify = "^4.0.1"
|
||||
requests = "^2.25.1"
|
||||
PyYAML = "^5.3.1"
|
||||
extruct = "^0.12.0"
|
||||
scrape-schema-recipe = "^0.1.3"
|
||||
python-multipart = "^0.0.5"
|
||||
fastapi-camelcase = "^1.0.2"
|
||||
bcrypt = "^3.2.0"
|
||||
python-jose = "^3.2.0"
|
||||
passlib = "^1.7.4"
|
||||
lxml = "4.6.2"
|
||||
Pillow = "^8.2.0"
|
||||
pathvalidate = "^2.4.1"
|
||||
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pylint = "^2.6.0"
|
||||
black = "^20.8b1"
|
||||
pytest = "^6.2.1"
|
||||
pytest-cov = "^2.11.0"
|
||||
mkdocs-material = "^7.0.2"
|
||||
flake8 = "^3.9.0"
|
||||
coverage = "^5.5"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "6.0"
|
||||
addopts = "-ra -q --cov=mealie"
|
||||
python_files = 'test_*'
|
||||
python_classes = '*Tests'
|
||||
python_functions = 'test_*'
|
||||
testpaths = [
|
||||
"tests",
|
||||
]
|
||||
|
||||
[tool.coverage.report]
|
||||
[tool.poetry]
|
||||
name = "mealie"
|
||||
version = "0.5.0b"
|
||||
description = "A Recipe Manager"
|
||||
authors = ["Hayden <hay-kot@pm.me>"]
|
||||
license = "MIT"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
start = "mealie.app:main"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
aiofiles = "0.5.0"
|
||||
aniso8601 = "7.0.0"
|
||||
appdirs = "1.4.4"
|
||||
fastapi = "^0.63.0"
|
||||
uvicorn = {extras = ["standard"], version = "^0.13.0"}
|
||||
APScheduler = "^3.6.3"
|
||||
SQLAlchemy = "^1.3.22"
|
||||
Jinja2 = "^2.11.2"
|
||||
python-dotenv = "^0.15.0"
|
||||
python-slugify = "^4.0.1"
|
||||
requests = "^2.25.1"
|
||||
PyYAML = "^5.3.1"
|
||||
extruct = "^0.12.0"
|
||||
scrape-schema-recipe = "^0.1.3"
|
||||
python-multipart = "^0.0.5"
|
||||
fastapi-camelcase = "^1.0.2"
|
||||
bcrypt = "^3.2.0"
|
||||
python-jose = "^3.2.0"
|
||||
passlib = "^1.7.4"
|
||||
lxml = "4.6.2"
|
||||
Pillow = "^8.2.0"
|
||||
pathvalidate = "^2.4.1"
|
||||
apprise = "^0.9.2"
|
||||
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pylint = "^2.6.0"
|
||||
black = "^20.8b1"
|
||||
pytest = "^6.2.1"
|
||||
pytest-cov = "^2.11.0"
|
||||
mkdocs-material = "^7.0.2"
|
||||
flake8 = "^3.9.0"
|
||||
coverage = "^5.5"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "6.0"
|
||||
addopts = "-ra -q --cov=mealie"
|
||||
python_files = 'test_*'
|
||||
python_classes = '*Tests'
|
||||
python_functions = 'test_*'
|
||||
testpaths = [
|
||||
"tests",
|
||||
]
|
||||
|
||||
[tool.coverage.report]
|
||||
skip_empty = true
|
Loading…
Add table
Add a link
Reference in a new issue