Feature/authentication (#210)

* basic crud NOT SECURE

* refactor/database init on startup

* added scratch.py

* tests/user CRUD routes

* password hashing

* change app_config location

* bump python version

* formatting

* login ui starter

* change import from url design

* move components

* remove old snackbar

* refactor/Componenet folder structure rework

* refactor/remove old code

* refactor/rename componenets/js files

* remove console.logs

* refactor/ models to schema and sql to models

* new header styling for imports

* token request

* fix url scrapper

* refactor/rename schema files

* split routes file

* redesigned admin page

* enable relative imports for vue components

* refactor/switch to pages view

* add CamelCase package

* majors settings rework

* user management second pass

* super user CRUD

* refactor/consistent models names

* refactor/consistent model names

* password reset

* store refactor

* dependency update

* abstract button props

* profile page refactor

* basic password validation

* login form refactor/split v-container

* remo unused code

* hide editor buttons when not logged in

* mkdocs dev dependency

* v0.4.0 docs update

* profile image upload

* additional token routes

* Smaller recipe cards for smaller viewports

* fix admin sidebar

* add users

* change to outlined

* theme card starter

* code cleanup

* signups

* signup pages

* fix #194

* fix #193

* clarify mealie_port

* fix #184

* fixes #178

* fix blank card error on meal-plan creator

* admin signup

* formatting

* improved search bar

* improved search bar

* refresh token on page refresh

* allow mealplan with no categories

* fix card layout

* remove cdn dependencies

* start on groups

* Fixes #196

* recipe databse refactor

* changelog draft

* database refactoring

* refactor recipe schema/model

* site settings refactor

* continued model refactor

* merge docs changes from master

* site-settings work

* cleanup + tag models

* notes

* typo

* user table

* sign up data validation

* package updates

* group store init

* Fix home page settings

* group admin init

* group dashboard init

* update deps

* formatting

* bug / added libffi-dev

* pages refactor

* fix mealplan

* docs update

* multi group supporot for job scheduler

* formatting

* formatting

* home-page redesign

* set background for docs darkmode

* code cleanup

* docs refactor

* v0.4.0 image

* mkdocs port change

* formatting

* Fix Meal-Plan Today

* fix webhook bug

* fix meal plan this week

* export users

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-03-15 22:15:42 -08:00 committed by GitHub
commit 44935ea72d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 309 additions and 163 deletions

View file

@ -122,4 +122,4 @@ Project Link: [https://github.com/hay-kot/mealie](https://github.com/hay-kot/mea
[license-url]: https://github.com/hay-kot/mealie/blob/master/LICENSE.txt [license-url]: https://github.com/hay-kot/mealie/blob/master/LICENSE.txt
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555 [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555
[linkedin-url]: https://linkedin.com/in/hay-kot [linkedin-url]: https://linkedin.com/in/hay-kot
[product-screenshot]: docs/docs/img/home_screenshot.png [product-screenshot]: docs/docs/assets/img/home_screenshot.png

View file

@ -36,7 +36,7 @@ services:
image: squidfunk/mkdocs-material image: squidfunk/mkdocs-material
restart: always restart: always
ports: ports:
- 9923:8000 - 9922:8000
volumes: volumes:
- ./docs:/docs - ./docs:/docs

View file

Before

Width:  |  Height:  |  Size: 14 MiB

After

Width:  |  Height:  |  Size: 14 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 MiB

After

Width:  |  Height:  |  Size: 2.9 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 30 MiB

After

Width:  |  Height:  |  Size: 30 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 8.4 MiB

After

Width:  |  Height:  |  Size: 8.4 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 35 MiB

After

Width:  |  Height:  |  Size: 35 MiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

View file

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 7 KiB

After

Width:  |  Height:  |  Size: 7 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 KiB

View file

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 350 KiB

After

Width:  |  Height:  |  Size: 350 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

View file

@ -1,6 +1,23 @@
# v0.4.0 Whoa, What a Release! [DRAFT] # v0.4.0 Whoa, What a Release! [DRAFT]
### Bug Fixes **App Version: v0.4.0**
**Database Version: v0.4.0**
## Breaking Changes
!!! error "Breaking Changes"
#### Database
A new database will be created. You must export your data and then import it after upgrading.
#### Site Settings
With the addition of group settings and a re-write of the database layer of the application backend, there is no migration path for your current site settings. Webhooks Settings, Meal Plan Categories are now managed by groups. Site settings, mainly homepage settings, are not site specific and managed by administrators.
#### ENV Variables
Names have been changed to be more consistent with industry standards. See the [Installation Page](/getting-started/install/) for new parameters.
## Bug Fixes
- Fixed Search Results Limited to 100 - #198 - Fixed Search Results Limited to 100 - #198
- Fixed Recette from marmiton.org not fully scrapped - #196 - Fixed Recette from marmiton.org not fully scrapped - #196
- Fixed Unable to get a page to load - #194 - Fixed Unable to get a page to load - #194
@ -10,9 +27,21 @@
- Fixed Original URL not saved to imported recipe in 0.3.0-dev - #183 - Fixed Original URL not saved to imported recipe in 0.3.0-dev - #183
- Fixed "IndexError: list index out of range" when viewing shopping list for meal plan containing days without a recipe selected - #178 - Fixed "IndexError: list index out of range" when viewing shopping list for meal plan containing days without a recipe selected - #178
### Features and Improvements ## Features and Improvements
#### User Authentication ### General
- Documentation Rewrite
- New Documentation
- Landing Page
- Custom Caddy Configuration
- User Management
- Introduction
- Updated Documentation
- Everything!
- The API Reference is now better embedded inside of the docs
- New default external port in documentation (Port 9000 -> 9925). This is only the port exposed by the host to the docker image. It doesn't change any existing functionality.
### User Authentication
- Authentication! Tons of stuff went into creating a flexible authentication platform for a lot of different use cases. Review the documentation for more information on how to use the authentication, and how everything works together. More complex management of recipes and user restrictions are in the works, but this is a great start! Some key features include - Authentication! Tons of stuff went into creating a flexible authentication platform for a lot of different use cases. Review the documentation for more information on how to use the authentication, and how everything works together. More complex management of recipes and user restrictions are in the works, but this is a great start! Some key features include
- Sign Up Links - Sign Up Links
- Admin and User Roles - Admin and User Roles
@ -20,14 +49,14 @@
- Group Management - Group Management
- Create/Edit/Delete Restrictions - Create/Edit/Delete Restrictions
#### UI Improvements ### UI Improvements
- Completed Redesign of the Admin Panel - Completed Redesign of the Admin Panel
- Profile Pages - Profile Pages
- Side Panel Menu - Side Panel Menu
- Improved UI for Recipe Search - Improved UI for Recipe Search
- Language selector is now displayed on all pages and does not require an account - Language selector is now displayed on all pages and does not require an account
#### Recipe Data ### Recipe Data
- Recipe Database Refactoring. Tons of new information is now stored for recipes in the database. Not all is accessible via the UI, but it's coming. - Recipe Database Refactoring. Tons of new information is now stored for recipes in the database. Not all is accessible via the UI, but it's coming.
- Nutrition Information - Nutrition Information
- calories - calories
@ -40,7 +69,7 @@
- "categories" has been migrated to "recipeCategory" to adhere closer to the standard schema - "categories" has been migrated to "recipeCategory" to adhere closer to the standard schema
- "tool" - a list of tools used for the recipe - "tool" - a list of tools used for the recipe
#### Behind the Scenes ### Behind the Scenes
- Removed CDN dependencies - Removed CDN dependencies
- Database Model Refactoring - Database Model Refactoring
- File/Folder Name Refactoring - File/Folder Name Refactoring

View file

@ -1,8 +1,5 @@
# Contributing to Mealie # Contributing to Mealie
!!! Warning
It should be known going into this that this is my first open source project, and my first public github repo I'm actively managing. If something does not make sense, or is not best practice. PLEASE feel free to reach out and let me know. I'm all about improving workflow and making it easier for contributors.
[Please Join the Discord](https://discord.gg/R6QDyJgbD2). We are building a community of developers working on the project. [Please Join the Discord](https://discord.gg/R6QDyJgbD2). We are building a community of developers working on the project.
## We Develop with Github ## We Develop with Github

View file

@ -1,15 +1,16 @@
# Contributing with translations # Contributing with Translations
Having Mealie in different language could help the adaption of Mealie. Translations can be a great way for non-coders to contribute to Mealie. Mealie supports a framework for the community to contribute different translations. Translations can be a great way for non-coders to contribute to project.
## Is Mealie missing in your language? ## My Language Is Missing
If your language is missing, you can add it, by beginning to translate. We use a Vue-i18n in json files. Copy frontend/src/locales/en.json to get started. If your language is missing, you can add it, by beginning to translate. We use a Vue-i18n in json files. Copy frontend/src/locales/en.json to get started.
## Improving translations ## Improving Translations
If your language is missing the translation for some strings, you can help out by adding a translation for that string. If you find a string you think could be improved, please feel free to do so. If your language is missing the translation for some strings, you can help out by adding a translation for that string. If you find a string you think could be improved, please feel free to do so.
## Tooling ## Tooling
Currently we use Vue-i18n for translations. Translations are stored in json format located in [frontend/src/locales](https://github.com/hay-kot/mealie/tree/master/frontend/src/locales). Currently we use Vue-i18n for translations. Translations are stored in json format located in [frontend/src/locales](https://github.com/hay-kot/mealie/tree/master/frontend/src/locales).
If you have experience with a good Translation Management System, please feel free to chime in on the [Discord](https://discord.gg/R6QDyJgbD2), as such a system could be helpful as the projects grow. If you have experience with a good Translation Management System, please feel free to chime in on the [Discord](https://discord.gg/R6QDyJgbD2), as such a system could be helpful as the projects grow.
Until then, [i18n Ally for VScode](https://marketplace.visualstudio.com/items?itemName=antfu.i18n-ally) is recommended to aid in translating. It also has a nice feature, which shows translations in-place when editing code. Until then, [i18n Ally for VScode](https://marketplace.visualstudio.com/items?itemName=antfu.i18n-ally) is recommended to aid in translating. It also has a nice feature, which shows translations in-place when editing code.
i18n Ally will also show which languages is missing translations. i18n Ally will also show which languages is missing translations.

View file

@ -6,7 +6,7 @@ Recipes extras are a key feature of the Mealie API. They allow you to create cus
For example you could add `{"message": "Remember to thaw the chicken"}` to a recipe and use the webhooks built into mealie to send that message payload to a destination to be processed. For example you could add `{"message": "Remember to thaw the chicken"}` to a recipe and use the webhooks built into mealie to send that message payload to a destination to be processed.
![api-extras-gif](../gifs/api-extras.gif) ![api-extras-gif](../assets/gifs/api-extras.gif)
## Examples ## Examples

View file

@ -4,23 +4,27 @@ To deploy docker on your local network it is highly recommended to use docker to
[Get Docker](https://docs.docker.com/get-docker/) [Get Docker](https://docs.docker.com/get-docker/)
[Mealie Docker Image](https://hub.docker.com/r/hkotel/mealie) [Mealie on Dockerhub](https://hub.docker.com/r/hkotel/mealie)
- linux/amd64
- linux/arm/v7
- linux/arm64
## Quick Start - Docker CLI ## Quick Start - Docker CLI
Deployment with the Docker CLI can be done with `docker run` and specify the database type, in this case `sqlite`, setting the exposed port `9000`, mounting the current directory, and pull the latest image. After the image is up an running you can navigate to http://your.ip.addres:9000 and you'll should see mealie up and running! Deployment with the Docker CLI can be done with `docker run` and specify the database type, in this case `sqlite`, setting the exposed port `9925`, mounting the current directory, and pull the latest image. After the image is up an running you can navigate to http://your.ip.addres:9925 and you'll should see mealie up and running!
```shell ```shell
docker run \ docker run \
-e db_type='sqlite' \ -e DB_TYPE='sqlite' \
-p 9000:80 \ -p 9925:80 \
-v `pwd`:'/app/data/' \ -v `pwd`:'/app/data/' \
hkotel/mealie:latest hkotel/mealie:latest
``` ```
## Docker Compose with SQLite ## Docker Compose with SQLite
Deployment with docker-compose is the recommended method for deployment. The example below will create an instance of mealie available on port `9000` with the data volume mounted from the local directory. To use, create a docker-compose.yml file, paste the contents below and save. In the terminal run `docker-compose up -d` to start the container. Deployment with docker-compose is the recommended method for deployment. The example below will create an instance of mealie available on port `9925` with the data volume mounted from the local directory. To use, create a docker-compose.yml file, paste the contents below and save. In the terminal run `docker-compose up -d` to start the container.
```yaml ```yaml
version: "3.1" version: "3.1"
@ -30,9 +34,9 @@ services:
image: hkotel/mealie:latest image: hkotel/mealie:latest
restart: always restart: always
ports: ports:
- 9000:80 - 9925:80
environment: environment:
db_type: sqlite DB_TYPE: sqlite
TZ: America/Anchorage TZ: America/Anchorage
volumes: volumes:
- ./mealie/data/:/app/data - ./mealie/data/:/app/data
@ -41,13 +45,46 @@ services:
## Env Variables ## Env Variables
| Variables | default | description | | Variables | Default | Description |
| ----------- | ------- | ----------------------------------------------------------------------------------- | | ---------------- | -------- | ----------------------------------------------------------------------------------- |
| db_type | sqlite | The database type to be used. Current Options 'sqlite' | | DB_TYPE | sqlite | The database type to be used. Current Options 'sqlite' |
| mealie_port | 9000 | The port exposed by backend API. **do not change this if you're running in docker** | | API_PORT | 9000 | The port exposed by backend API. **do not change this if you're running in docker** |
| api_docs | True | Turns on/off access to the API documentation locally. | | API_DOCS | True | Turns on/off access to the API documentation locally. |
| TZ | UTC | You should set your time zone accordingly so the date/time features work correctly | | DEFAULT_PASSWORD | ChangeMe | The default password for all users created in Mealie |
| TZ | UTC | Must be set to get correct date/time on the server |
## Deployed as a Python Application ## Deployed as a Python Application
Alternatively, this project is built on Python and SQLite. If you are dead set on deploying on a linux machine you can run this in an python virtual env. Provided that you know thats how you want to host the application, I'll assume you know how to do that. I may or may not get around to writing this guide. I'm open to pull requests if anyone has a good guide on it. Alternatively, this project is built on Python and SQLite. If you are dead set on deploying on a linux machine you can run this in an python virtual env. Provided that you know thats how you want to host the application, I'll assume you know how to do that. I may or may not get around to writing this guide. I'm open to pull requests if anyone has a good guide on it.
## Advanced
!!! warning "Not Required"
The items below are completely optional and are not required to manage or install your Mealie instance.
### Custom Caddy File
The Docker image provided by Mealie contains both the API and the html bundle in one convenient image. This is done by using a proxy server to serve different parts of the application depending on the URL/URI. Requests sent to `/api/*` or `/docs` will be directed to the API, anything else will be served the static web files. Below is the default Caddyfile that is used to proxy requests. You can override this file by mounting an alternative Caddyfile to `/app/Caddyfile`.
```
{
auto_https off
admin off
}
:80 {
@proxied path /api/* /docs /openapi.json
root * /app/dist
encode gzip
uri strip_suffix /
handle @proxied {
reverse_proxy http://127.0.0.1:9000
}
handle {
try_files {path}.html {path} /
file_server
}
}
```

View file

@ -16,11 +16,11 @@ Mealie is a self hosted recipe manager and meal planner with a RestAPI backend a
- 🏷️ Tag recipes with categories or tags to flexible sorting - 🏷️ Tag recipes with categories or tags to flexible sorting
- ⬇️ Import recipes from around the web by URL - ⬇️ Import recipes from around the web by URL
- 📱 Beautiful Mobile Views - 📱 Beautiful Mobile Views
- 📆 Create meal plans - 📆 Create Meal Plans
- 🛒 Generate shopping lists from meal plans - 🛒 Generate shopping lists from Meal Plans
- 🐳 Easy setup with Docker - 🐳 Easy setup with Docker
- 🎨 Customize your interface with color themes layouts - 🎨 Customize your interface with color themes layouts
- ✉️ Export and import all your data in any formating with Jinja Tempaltes - ✉️ Export all your data in any format with Jinja2 Templates, with easy data restoration from the UI.
- 🌍 localized in many languages - 🌍 localized in many languages
- Plus tons more! - Plus tons more!
- Flexible API - Flexible API
@ -39,7 +39,12 @@ Mealie is a self hosted recipe manager and meal planner with a RestAPI backend a
An API allows integration into applications like [Home Assistant](https://www.home-assistant.io/) that can act as notification engines to provide custom notifications based of Meal Plan data to remind you to defrost the chicken, marinade the steak, or start the CrockPot. Additionally, you can access nearly any backend service via the API giving you total control to extend the application. To explore the API spin up your server and navigate to http://yourserver.com/docs for interactive API documentation. An API allows integration into applications like [Home Assistant](https://www.home-assistant.io/) that can act as notification engines to provide custom notifications based of Meal Plan data to remind you to defrost the chicken, marinade the steak, or start the CrockPot. Additionally, you can access nearly any backend service via the API giving you total control to extend the application. To explore the API spin up your server and navigate to http://yourserver.com/docs for interactive API documentation.
### Why a Database? ### Why a Database?
Some users of static-site generator applications have expressed concerns about their data being stuck in a database. Considering this is a new project it is a valid concern to be worried about your data. Mealie specifically addresses this concern by provided automatic daily backups that export your data in json, plain-text markdown files, and/or custom Jinja2 templates. This puts you in controls of how your data is represented when exported from Mealie, which means you can easily migrate to any other service provided Mealie doesn't work for you. Some users of static-site generator applications like ChowDown have expressed concerns about their data being stuck in a database. Considering this is a new project it is a valid concern to be worried about your data. Mealie specifically addresses this concern by provided automatic daily backups that export your data in json, plain-text markdown files, and/or custom Jinja2 templates. **This puts you in controls of how your data is represented** when exported from Mealie, which means you can easily migrate to any other service provided Mealie doesn't work for you.
As to why we need a database?
- **Developer Experience:** Without a database a lot of the work to maintain your data is taken on by the developer instead of a battle tested platform for storing data.
- **Multi User Support:** With a solid database as backend storage for your data Mealie can better support multi-user sites and avoid read/write access errors when multiple actions are taken at the same time.
## Built With ## Built With

View file

@ -1,5 +1,5 @@
# Using iOS Shortcuts with Mealie # Using iOS Shortcuts with Mealie
![](../img/iphone-image.png){: align=right style="height:400px;width:400px"} ![](../assets/img/iphone-image.png){: align=right style="height:400px;width:400px"}
User [brasilikum](https://github.com/brasilikum) opened an issue on the main repo about how they had created an [iOS shortcut](https://github.com/hay-kot/mealie/issues/103) for interested users. This is a useful utility for iOS users who browse for recipes in their web browser from their devices. User [brasilikum](https://github.com/brasilikum) opened an issue on the main repo about how they had created an [iOS shortcut](https://github.com/hay-kot/mealie/issues/103) for interested users. This is a useful utility for iOS users who browse for recipes in their web browser from their devices.
@ -13,7 +13,7 @@ Don't know what an iOS shortcut is? Neither did I! Experienced iOS users may alr
Basically it is a visual scripting language that lets a user build an automation in a guided fashion. The automation can be [shared with anyone](https://www.icloud.com/shortcuts/6ae356d5fc644cfa8983a3c90f242fbb) but if it is a user creation, you'll have to jump through a few hoops to make an untrusted automation work on your device. In brasilikum's shortcut, you need to make changes for it to work. Recent updates to the project have changed some of the syntax and folder structure since its original creation. Basically it is a visual scripting language that lets a user build an automation in a guided fashion. The automation can be [shared with anyone](https://www.icloud.com/shortcuts/6ae356d5fc644cfa8983a3c90f242fbb) but if it is a user creation, you'll have to jump through a few hoops to make an untrusted automation work on your device. In brasilikum's shortcut, you need to make changes for it to work. Recent updates to the project have changed some of the syntax and folder structure since its original creation.
![screenshot](../img/ios-shortcut-image.jpg){: align=right style="height:500;width:400px"} ![screenshot](../assets/img/ios-shortcut-image.jpg){: align=right style="height:500;width:400px"}

View file

@ -9,5 +9,5 @@ To edit the meal in a meal plan simply select the edit button on the card in the
## Shopping Lists ## Shopping Lists
For any meal plan created you can view a breakdown of all the ingredients and use an experimental sort function to sort similarly ingredients. This is a very new feature and results of the auto sort may vary. For any meal plan created you can view a breakdown of all the ingredients and use an experimental sort function to sort similarly ingredients. This is a very new feature and results of the auto sort may vary.
![](../gifs/meal-plan-demo-v2.gif) ![](../assets/gifs/meal-plan-demo-v2.gif)

View file

@ -4,7 +4,7 @@
Adding a recipe can be as easy as copying the recipe URL into mealie and letting the web scrapper try to pull down the information. Currently this scraper is implemented with [scrape-schema-recipe package](https://pypi.org/project/scrape-schema-recipe/). You may have mixed results on some websites, especially with blogs or non specific recipe websites. See the bulk import Option below for another a convenient way to add blog style recipes into Mealie. Adding a recipe can be as easy as copying the recipe URL into mealie and letting the web scrapper try to pull down the information. Currently this scraper is implemented with [scrape-schema-recipe package](https://pypi.org/project/scrape-schema-recipe/). You may have mixed results on some websites, especially with blogs or non specific recipe websites. See the bulk import Option below for another a convenient way to add blog style recipes into Mealie.
![](../gifs/url-demo.gif) ![](../assets/gifs/url-demo.gif)
## Recipe Editor ## Recipe Editor
@ -12,12 +12,12 @@ Recipes can be edited and created via the UI. This is done with both a form base
You can also add a custom recipe with the UI editor built into the web view. You can also add a custom recipe with the UI editor built into the web view.
![](../gifs/editor-demo.gif) ![](../assets/gifs/editor-demo.gif)
## Bulk Import ## Bulk Import
Mealie also supports bulk import of recipe instructions and ingredients. Select "Bulk Add" in the editor and paste in your plain text data to be parsed. Each line is treated as one entry and will be appended to the existing ingredients or instructions if they exist. Empty lines will be stripped from the text. Mealie also supports bulk import of recipe instructions and ingredients. Select "Bulk Add" in the editor and paste in your plain text data to be parsed. Each line is treated as one entry and will be appended to the existing ingredients or instructions if they exist. Empty lines will be stripped from the text.
![](../gifs/bulk-add-demo.gif) ![](../assets/gifs/bulk-add-demo.gif)
## Schema ## Schema
Recipes are stored in the json-like format in mongoDB and then sent and edited in json format on the frontend. Each recipes uses [Recipe Schema](https://schema.org/Recipe) as a general guide with some additional properties specific to Mealie. Recipes are stored in the json-like format in mongoDB and then sent and edited in json format on the frontend. Each recipes uses [Recipe Schema](https://schema.org/Recipe) as a general guide with some additional properties specific to Mealie.

View file

@ -9,6 +9,10 @@
- Create a Backup and Download from the UI - Create a Backup and Download from the UI
- Upgrade - Upgrade
## Backing Up Your Data
[See Backups and Restore Section](/site-administration/backups-and-exports/) for details on backing up your data
## Docker ## Docker
For all setups using Docker the updating process look something like this For all setups using Docker the updating process look something like this

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

View file

@ -225,7 +225,7 @@
<div class="md-grid md-typeset"> <div class="md-grid md-typeset">
<div class="tx-hero"> <div class="tx-hero">
<div class="tx-hero__image"> <div class="tx-hero__image">
<img src="img/home_screenshot.png" draggable="false"> <img src="assets/img/home_screenshot.png" draggable="false">
</div> </div>
<div class="tx-hero__content"> <div class="tx-hero__content">
<h1> <h1>
@ -260,7 +260,7 @@
Import Recipes Import Recipes
</h2> </h2>
<p> <p>
Quickly and easily import recipes from sites around the web using the built in recipe scrapper. Quickly and easily import recipes from sites around the web using the built in <b>recipe scrapper</b>.
</p> </p>
</div> </div>
<div class="feature-item"> <div class="feature-item">
@ -271,7 +271,7 @@
Automatic Backups Automatic Backups
</h2> </h2>
<p> <p>
Keep your data safe with automatic backups in any format supported by Jinja2 Templates Keep your data safe with automatic backups in any format supported by <b>Jinja2</b> templates
</p> </p>
</div> </div>
<div class="feature-item"> <div class="feature-item">
@ -281,7 +281,7 @@
</svg> </svg>
Rich User Interface Rich User Interface
</h2> </h2>
<p> Use a beautiful and intuitive user interface to create, edit, and delete recipes </p> <p> Use a beautiful and intuitive user interface to create, edit, and delete recipes. Recipe editor supports <b>markdown syntax</b> </p>
</div> </div>
<div class="feature-item"> <div class="feature-item">
<h2> <h2>
@ -304,7 +304,7 @@
Users Users
</h2> </h2>
<p> <p>
Add new Users with sign-up links or simply create a new user in the admin panel. Add new users with sign-up links or simply create a new user in the admin panel.
</p> </p>
</div> </div>
<div class="feature-item"> <div class="feature-item">
@ -315,7 +315,7 @@
Groups Groups
</h2> </h2>
<p> <p>
Sort users into groups to share recipes with the whole family, but keep your Meal Plans separate Sort users into groups to share recipes with the whole family, but keep your Meal Plans separate.
</p> </p>
</div> </div>
<div class="feature-item"> <div class="feature-item">
@ -325,7 +325,7 @@
</svg> </svg>
Webhooks Webhooks
</h2> </h2>
<p> Schedule webhooks to send notifications to 3rd party services with todays Meal Plan </p> <p> Schedule webhooks to send notifications to 3rd party services with todays Meal Plan data. </p>
</div> </div>
<div class="feature-item"> <div class="feature-item">
<h2> <h2>
@ -334,7 +334,7 @@
</svg> </svg>
Open API Open API
</h2> </h2>
<p> API Driven application gives you full control of the backend server with interactive documentation</p> <p> <b>API Driven</b> application gives you full control of the backend server with interactive documentation</p>
</div> </div>
</div> </div>
</section> </section>

View file

@ -18,7 +18,7 @@ To import a backup it must be in your backups folder. If it is in the backup fol
## Demo ## Demo
![](../gifs/backup-demo-v1.gif) ![](../assets/gifs/backup-demo-v1.gif)
## API Examples ## API Examples
You can use the API to create and retrieve backups remotely from any server that can access the Mealie instance. This is useful for easily managing off-site backups via cron-job or other scheduled tasks. You can find interactive documentation for your API at https://your-mealie-instance.com/docs You can use the API to create and retrieve backups remotely from any server that can access the Mealie instance. This is useful for easily managing off-site backups via cron-job or other scheduled tasks. You can find interactive documentation for your API at https://your-mealie-instance.com/docs

View file

@ -10,6 +10,7 @@ Your sites settings panel can only be accessed by administrators. This where you
| Card Per Section | The amount of cards displayed in each section on the home page | | Card Per Section | The amount of cards displayed in each section on the home page |
| Home Page Sections | Category sections to include on the home page | | Home Page Sections | Category sections to include on the home page |
![Site Settings Image](../assets/img/site-settings.png)

View file

@ -7,13 +7,37 @@ As of version v0.4.0 users have limited functionality, but they will offer more
Mealie admins are super users that have access to all user data (excluding passwords). All admins can perform administrative tasks like adding users, resetting user passwords, backing up the database, migrating data, and managing site settings. Mealie admins are super users that have access to all user data (excluding passwords). All admins can perform administrative tasks like adding users, resetting user passwords, backing up the database, migrating data, and managing site settings.
**Admins Can**
- All User Actions
- Adjust Site Settings
- Create and Update Users
- Create and Update Groups
- Generate User Sign-up Links
- Migrate Data from other Services
- Backup Site Data
=== ":fontawesome-solid-user: Users" === ":fontawesome-solid-user: Users"
A single user created by an Admin that has basic privileges to edit their profile, create and edit recipes. A single user created by an Admin that has basic privileges to edit their profile, create and edit recipes.
**Users Can**
- Manage Their Profile
- Create, Edit, and Update Recipes
- Create, Edit, and Update Mealplans *(By Group)*
- Set Mealplan Categories
- Create and Schedule Webhooks *(By Group)*
=== ":fontawesome-solid-users: Groups" === ":fontawesome-solid-users: Groups"
User groups, or "family" groups are a collection of users that are associated together. Users belonging to the same group will share meal plans and webhook settings. This is currently the only feature of groups. User groups are a collection of users that are associated together. Typically used for separate households sharing a single instance.
**Groups Share**
- Mealplans
- Mealplan Settings
- Webhooks
@ -30,22 +54,29 @@ There are two ways to create users in Mealie.
### Manually Creating a User ### Manually Creating a User
In the Manage Users section you are able to create a user by providing the necessary information in the pop-up dialog. In the Manage Users section you are able to create a user by providing the necessary information in the pop-up dialog.
![Create User Image](../assets/img/add-user.png){: align=right style="height:50%;width:50%"}
- User Name - User Name
- Email - Email
- User Group - User Group
- If they are an Admin - If they are an Admin
When creating users manually, their password will be set from the default assigned by the ENV variable. When creating users manually, their password will be set from the default assigned by the ENV variable.
### Sign Up Links ### Sign Up Links
You can generate sign-up links in the Manage Users section. Select the "create link" button and provide the name of the link and if the user will be an administrator. Once a link is created it will populate in the table where you'll be able to see all active links, delete a link, and copy the link as needed. You can generate sign-up links in the Manage Users section. Select the "create link" button and provide the name of the link and if the user will be an administrator. Once a link is created it will populate in the table where you'll be able to see all active links, delete a link, and copy the link as needed.
![Sign Up Links Image](../assets/img/sign-up-links.png)
!!! tip !!! tip
When a link is used it is automatically removed from the database. When a link is used it is automatically removed from the database.
## Creating Groups ## Creating Groups
You can easily create and manage groups via the frontend in the admin panel under "Manage Users". Navigate to the groups tab and you'll find a "create group" button as well as a list of all groups in your database. To create a group, select the "create group" button and provide a name for the new group. Once created you can now assign users to the new group. You can easily create and manage groups via the frontend in the admin panel under "Manage Users". Navigate to the groups tab and you'll find a "create group" button as well as a list of all groups in your database. To create a group, select the "create group" button and provide a name for the new group. Once created you can now assign users to the new group.
![Group Management Panel](../assets/img/group-manager.png)
!!! tip !!! tip
User Groups can only be deleted if no users are apart of the group. If you want to delete a group, you must assign the users to another group before removing. User Groups can only be deleted if no users are apart of the group. If you want to delete a group, you must assign the users to another group before removing.

View file

@ -14,7 +14,7 @@ In as users profile they are able to
## Themes ## Themes
Color themes can be created and set from the UI in the users settings page. You can select an existing color theme or create a new one. On creation of a new color theme, the default colors will be used, then you can select and save as you'd like. By default the "default" theme will be loaded for all new users visiting the site. All created color themes are available to all users of the site. Theme Colors will be set for both light and dark modes. Color themes can be created and set from the UI in the users settings page. You can select an existing color theme or create a new one. On creation of a new color theme, the default colors will be used, then you can select and save as you'd like. By default the "default" theme will be loaded for all new users visiting the site. All created color themes are available to all users of the site. Theme Colors will be set for both light and dark modes.
![](../gifs/theme-demo-v2.gif) ![](../assets/gifs/theme-demo-v2.gif)
!!! tip !!! tip
Theme data is stored in local storage in the browser. Calling "Save colors and apply theme will refresh the local storage with the selected theme as well save the theme to the database. Theme data is stored in local storage in the browser. Calling "Save colors and apply theme will refresh the local storage with the selected theme as well save the theme to the database.

View file

@ -8,7 +8,7 @@ theme:
- navigation.sections - navigation.sections
- navigation.tabs - navigation.tabs
- navigation.tabs.sticky - navigation.tabs.sticky
favicon: img/favicon.png favicon: assets/img/favicon.png
name: material name: material
icon: icon:
logo: material/silverware-variant logo: material/silverware-variant
@ -28,7 +28,7 @@ markdown_extensions:
- pymdownx.superfences - pymdownx.superfences
extra_css: extra_css:
- stylesheets/custom.css - assets/stylesheets/custom.css
repo_url: https://github.com/hay-kot/mealie repo_url: https://github.com/hay-kot/mealie
repo_name: hay-kot/mealie repo_name: hay-kot/mealie

View file

@ -16,6 +16,7 @@ const mealPlanURLs = {
export default { export default {
async create(postBody) { async create(postBody) {
console.log(postBody);
let response = await apiReq.post(mealPlanURLs.create, postBody); let response = await apiReq.post(mealPlanURLs.create, postBody);
return response; return response;
}, },

View file

@ -17,7 +17,9 @@
:src="getImage(meal.slug)" :src="getImage(meal.slug)"
@click="openSearch(index)" @click="openSearch(index)"
></v-img> ></v-img>
<v-card-title class="my-n3 mb-n6">{{ $d( new Date(meal.date), 'short' ) }}</v-card-title> <v-card-title class="my-n3 mb-n6">
{{ $d(new Date(meal.date.split("-")), "short") }}
</v-card-title>
<v-card-subtitle> {{ meal.name }}</v-card-subtitle> <v-card-subtitle> {{ meal.name }}</v-card-subtitle>
</v-card> </v-card>
</v-hover> </v-hover>

View file

@ -25,8 +25,8 @@
> >
<v-card class="mt-1"> <v-card class="mt-1">
<v-card-title> <v-card-title>
{{ $d(new Date(mealplan.startDate), "short") }} - {{ $d(new Date(mealplan.startDate.split("-")), "short") }} -
{{ $d(new Date(mealplan.endDate), "short") }} {{ $d(new Date(mealplan.endDate.split("-")), "short") }}
</v-card-title> </v-card-title>
<v-list nav> <v-list nav>
<v-list-item-group color="primary"> <v-list-item-group color="primary">
@ -44,7 +44,7 @@
<v-list-item-content> <v-list-item-content>
<v-list-item-title v-text="meal.name"></v-list-item-title> <v-list-item-title v-text="meal.name"></v-list-item-title>
<v-list-item-subtitle <v-list-item-subtitle
v-text="$d(new Date(meal.date), 'short')" v-text="$d(new Date(meal.date.split('-')), 'short')"
> >
</v-list-item-subtitle> </v-list-item-subtitle>
</v-list-item-content> </v-list-item-content>

View file

@ -73,8 +73,8 @@ export default {
return `${dow}, ${month} ${day}`; return `${dow}, ${month} ${day}`;
}, },
getDateAsPythonDate(dateObject) { getDateAsPythonDate(dateObject) {
const month = dateObject.getMonth() + 1; const month = dateObject.getUTCMonth() + 1;
const day = dateObject.getDate(); const day = dateObject.getUTCDate();
const year = dateObject.getFullYear(); const year = dateObject.getFullYear();
return `${year}-${month}-${day}`; return `${year}-${month}-${day}`;

View file

@ -5,6 +5,7 @@ from schema.settings import SiteSettings as SiteSettingsSchema
from schema.sign_up import SignUpOut from schema.sign_up import SignUpOut
from schema.theme import SiteTheme from schema.theme import SiteTheme
from schema.user import GroupInDB, UserInDB from schema.user import GroupInDB, UserInDB
from sqlalchemy.orm import load_only
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
from db.db_base import BaseDocument from db.db_base import BaseDocument
@ -94,6 +95,27 @@ class _Groups(BaseDocument):
self.orm_mode = True self.orm_mode = True
self.schema = GroupInDB self.schema = GroupInDB
def get_meals(
self, session: Session, match_value: str, match_key: str = "name"
) -> list[MealPlanInDB]:
"""A Helper function to get the group from the database and return a sorted list of
Args:
session (Session): SqlAlchemy Session
match_value (str): Match Value
match_key (str, optional): Match Key. Defaults to "name".
Returns:
list[MealPlanInDB]: [description]
"""
group: GroupInDB = (
session.query(self.sql_model)
.filter_by(**{match_key: match_value})
.one_or_none()
)
return sorted(group.mealplans, key=lambda mealplan: mealplan.startDate)
class _SignUps(BaseDocument): class _SignUps(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:

View file

@ -29,7 +29,7 @@ class MealPlanModel(SqlAlchemyBase, BaseMixins):
uid = sa.Column(sa.Integer, primary_key=True, unique=True) #! Probably Bad? uid = sa.Column(sa.Integer, primary_key=True, unique=True) #! Probably Bad?
startDate = sa.Column(sa.Date) startDate = sa.Column(sa.Date)
endDate = sa.Column(sa.Date) endDate = sa.Column(sa.Date)
meals: List[Meal] = orm.relationship(Meal, cascade="all, delete") meals: List[Meal] = orm.relationship(Meal, cascade="all, delete, delete-orphan")
group_id = sa.Column(sa.String, sa.ForeignKey("groups.id")) group_id = sa.Column(sa.String, sa.ForeignKey("groups.id"))
group = orm.relationship("Group", back_populates="mealplans") group = orm.relationship("Group", back_populates="mealplans")
@ -42,7 +42,6 @@ class MealPlanModel(SqlAlchemyBase, BaseMixins):
self.meals = [Meal(**meal) for meal in meals] self.meals = [Meal(**meal) for meal in meals]
def update(self, session, startDate, endDate, meals, uid, group) -> None: def update(self, session, startDate, endDate, meals, uid, group) -> None:
MealPlanModel._sql_remove_list(session, [Meal], uid)
self.__init__( self.__init__(
startDate=startDate, startDate=startDate,

View file

@ -1,3 +1,5 @@
import datetime
from db.database import db from db.database import db
from db.db_setup import generate_session from db.db_setup import generate_session
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
@ -17,9 +19,8 @@ def get_all_meals(
session: Session = Depends(generate_session), session: Session = Depends(generate_session),
): ):
""" Returns a list of all available Meal Plan """ """ Returns a list of all available Meal Plan """
print(current_user.group)
group_entry: GroupInDB = db.groups.get(session, current_user.group, "name") return db.groups.get_meals(session, current_user.group)
return group_entry.mealplans
@router.post("/create") @router.post("/create")
@ -57,17 +58,27 @@ def delete_meal_plan(plan_id, session: Session = Depends(generate_session)):
@router.get("/this-week", response_model=MealPlanInDB) @router.get("/this-week", response_model=MealPlanInDB)
def get_this_week(session: Session = Depends(generate_session)): def get_this_week(
session: Session = Depends(generate_session),
current_user: UserInDB = Depends(manager),
):
""" Returns the meal plan data for this week """ """ Returns the meal plan data for this week """
return db.meals.get_all(session, limit=1, order_by="startDate") return db.groups.get_meals(session, current_user.group)[0]
@router.get("/today", tags=["Meal Plan"]) @router.get("/today", tags=["Meal Plan"])
def get_today(session: Session = Depends(generate_session)): def get_today(
session: Session = Depends(generate_session),
current_user: UserInDB = Depends(manager),
):
""" """
Returns the recipe slug for the meal scheduled for today. Returns the recipe slug for the meal scheduled for today.
If no meal is scheduled nothing is returned If no meal is scheduled nothing is returned
""" """
return get_todays_meal(session) group_in_db: GroupInDB = db.groups.get(session, current_user.group, "name")
recipe = get_todays_meal(session, group_in_db)
print(datetime.date.today())
return recipe.slug

View file

@ -2,19 +2,21 @@ import json
import shutil import shutil
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Union
from core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR, TEMPLATE_DIR from core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR, TEMPLATE_DIR
from db.database import db from db.database import db
from db.db_setup import create_session from db.db_setup import create_session
from fastapi.logger import logger from fastapi.logger import logger
from jinja2 import Template from jinja2 import Template
from pydantic.main import BaseModel
from schema.recipe import Recipe from schema.recipe import Recipe
class ExportDatabase: class ExportDatabase:
def __init__(self, session, tag=None, templates=None) -> None: def __init__(self, tag=None, templates=None) -> None:
"""Export a Mealie database. Export interacts directly with class objects and can be used """Export a Mealie database. Export interacts directly with class objects and can be used
with any supported backend database platform. By default tags are timestands, and no with any supported backend database platform. By default tags are timestamps, and no
Jinja2 templates are rendered Jinja2 templates are rendered
@ -27,14 +29,9 @@ class ExportDatabase:
else: else:
export_tag = datetime.now().strftime("%Y-%b-%d") export_tag = datetime.now().strftime("%Y-%b-%d")
self.session = session
self.main_dir = TEMP_DIR.joinpath(export_tag) self.main_dir = TEMP_DIR.joinpath(export_tag)
self.img_dir = self.main_dir.joinpath("images") self.img_dir = self.main_dir.joinpath("images")
self.recipe_dir = self.main_dir.joinpath("recipes")
self.themes_dir = self.main_dir.joinpath("themes")
self.settings_dir = self.main_dir.joinpath("settings")
self.templates_dir = self.main_dir.joinpath("templates") self.templates_dir = self.main_dir.joinpath("templates")
self.mealplans_dir = self.main_dir.joinpath("mealplans")
try: try:
self.templates = [TEMPLATE_DIR.joinpath(x) for x in templates] self.templates = [TEMPLATE_DIR.joinpath(x) for x in templates]
@ -45,71 +42,50 @@ class ExportDatabase:
required_dirs = [ required_dirs = [
self.main_dir, self.main_dir,
self.img_dir, self.img_dir,
self.recipe_dir,
self.themes_dir,
self.settings_dir,
self.templates_dir, self.templates_dir,
self.mealplans_dir,
] ]
for dir in required_dirs: for dir in required_dirs:
dir.mkdir(parents=True, exist_ok=True) dir.mkdir(parents=True, exist_ok=True)
def export_recipes(self): def export_templates(self, recipe_list: list[BaseModel]):
all_recipes = db.recipes.get_all(self.session)
for recipe in all_recipes:
recipe: Recipe
logger.info(f"Backing Up Recipes: {recipe}")
filename = recipe.slug + ".json"
file_path = self.recipe_dir.joinpath(filename)
ExportDatabase._write_json_file(recipe.dict(), file_path)
if self.templates:
self._export_template(recipe)
def _export_template(self, recipe_data: Recipe):
for template_path in self.templates: for template_path in self.templates:
out_dir = self.templates_dir.joinpath(template_path.name)
out_dir.mkdir(parents=True, exist_ok=True)
with open(template_path, "r") as f: with open(template_path, "r") as f:
template = Template(f.read()) template = Template(f.read())
filename = recipe_data.name + template_path.suffix for recipe in recipe_list:
out_file = self.templates_dir.joinpath(filename) filename = recipe.slug + template_path.suffix
out_file = out_dir.joinpath(filename)
content = template.render(recipe=recipe_data) content = template.render(recipe=recipe)
with open(out_file, "w") as f: with open(out_file, "w") as f:
f.write(content) f.write(content)
def export_images(self): def export_images(self):
for file in IMG_DIR.iterdir(): for file in IMG_DIR.iterdir():
shutil.copy(file, self.img_dir.joinpath(file.name)) shutil.copy(file, self.img_dir.joinpath(file.name))
def export_settings(self): def export_items(self, items: list[BaseModel], folder_name: str, export_list=True):
all_settings = db.settings.get(self.session, "main") items = [x.dict() for x in items]
out_file = self.settings_dir.joinpath("settings.json") out_dir = self.main_dir.joinpath(folder_name)
ExportDatabase._write_json_file(all_settings, out_file) out_dir.mkdir(parents=True, exist_ok=True)
def export_themes(self): if export_list:
all_themes = db.themes.get_all(self.session) ExportDatabase._write_json_file(
if all_themes: items, out_dir.joinpath(f"{folder_name}.json")
out_file = self.themes_dir.joinpath("themes.json") )
ExportDatabase._write_json_file(all_themes, out_file) else:
for item in items:
def export_meals(self): ExportDatabase._write_json_file(
#! Problem Parseing Datetime Objects... May come back to this item, out_dir.joinpath(f"{item.get('name')}.json")
meal_plans = db.meals.get_all(self.session) )
if meal_plans:
meal_plans = [x.dict() for x in meal_plans]
out_file = self.mealplans_dir.joinpath("mealplans.json")
ExportDatabase._write_json_file(meal_plans, out_file)
@staticmethod @staticmethod
def _write_json_file(data: dict, out_file: Path): def _write_json_file(data: Union[dict, list], out_file: Path):
json_data = json.dumps(data, indent=4, default=str) json_data = json.dumps(data, indent=4, default=str)
with open(out_file, "w") as f: with open(out_file, "w") as f:
@ -131,19 +107,32 @@ def backup_all(
export_recipes=True, export_recipes=True,
export_settings=True, export_settings=True,
export_themes=True, export_themes=True,
export_users=True,
export_groups=True,
): ):
db_export = ExportDatabase(session=session, tag=tag, templates=templates) db_export = ExportDatabase(tag=tag, templates=templates)
if export_users:
all_users = db.users.get_all(session)
db_export.export_items(all_users, "users")
if export_groups:
all_groups = db.groups.get_all(session)
db_export.export_items(all_groups, "groups")
if export_recipes: if export_recipes:
db_export.export_recipes() all_recipes = db.recipes.get_all(session)
db_export.export_items(all_recipes, "recipes", export_list=False)
db_export.export_templates(all_recipes)
db_export.export_images() db_export.export_images()
if export_settings: if export_settings:
db_export.export_settings() all_settings = db.settings.get_all(session)
db_export.export_items(all_settings, "settings")
if export_themes: if export_themes:
db_export.export_themes() all_themes = db.themes.get_all(session)
# db_export.export_meals() db_export.export_items(all_themes, "themes")
return db_export.finish_export() return db_export.finish_export()

View file

@ -1,14 +1,23 @@
from datetime import date, timedelta from datetime import date, timedelta, timezone
from typing import Union
import pytz
from db.database import db from db.database import db
from db.db_setup import create_session
from pydantic.tools import T
from schema.meal import MealIn, MealOut, MealPlanIn, MealPlanInDB, MealPlanProcessed from schema.meal import MealIn, MealOut, MealPlanIn, MealPlanInDB, MealPlanProcessed
from schema.recipe import Recipe from schema.recipe import Recipe
from schema.user import GroupInDB
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
def process_meals(session: Session, meal_plan_base: MealPlanIn) -> MealPlanProcessed: def process_meals(session: Session, meal_plan_base: MealPlanIn) -> MealPlanProcessed:
meals = [] meals = []
for x, meal in enumerate(meal_plan_base.meals): for x, meal in enumerate(meal_plan_base.meals):
# europe = pytz.timezone("America/Anchorage")
# d = europe.localize(meal_plan_base.startDate)
# print(d)
meal: MealIn meal: MealIn
try: try:
recipe: Recipe = db.recipes.get(session, meal.slug) recipe: Recipe = db.recipes.get(session, meal.slug)
@ -37,10 +46,34 @@ def process_meals(session: Session, meal_plan_base: MealPlanIn) -> MealPlanProce
) )
def get_todays_meal(session): def get_todays_meal(session: Session, group: Union[int, GroupInDB]) -> Recipe:
meal_plan: MealPlanInDB = db.groups.get(session, limit=1, order_by="startDate") """Returns the given mealplan for today based off the group. If the group
Type is of type int, then a query will be made to the database to get the
grop object."
for meal in meal_plan.meals: Args:
meal: MealOut session (Session): SqlAlchemy Session
if meal.date == date.today(): group (Union[int, GroupInDB]): Either the id of the group or the GroupInDB Object
return meal.slug
Returns:
Recipe: Pydantic Recipe Object
"""
session = session if session else create_session()
if isinstance(group, int):
group: GroupInDB = db.groups.get(session, group)
today_slug = None
for mealplan in group.mealplans:
mealplan: MealPlanInDB
for meal in mealplan.meals:
meal: MealOut
if meal.date == date.today():
today_slug = meal.slug
break
if today_slug:
return db.recipes.get(session, today_slug)
else:
return None

View file

@ -55,18 +55,6 @@ class ScheduledFunction:
args=args, args=args,
) )
logger.info("New Function Scheduled")
logger.info(scheduler.print_jobs())
logger.info("----INIT SCHEDULE OBJECT-----")
JOB_STORE = {
"backup_job": ScheduledFunction(
scheduler, auto_backup_job, Cron(hours=00, minutes=00), "backups"
),
}
def init_webhook_schedule(scheduler, job_store: dict): def init_webhook_schedule(scheduler, job_store: dict):
session = create_session() session = create_session()
@ -88,12 +76,19 @@ def init_webhook_schedule(scheduler, job_store: dict):
) )
session.close() session.close()
logger.info("Init Webhook Schedule \n", scheduler.print_jobs())
return job_store return job_store
logger.info("----INIT SCHEDULE OBJECT-----")
JOB_STORE = {
"backup_job": ScheduledFunction(
scheduler, auto_backup_job, Cron(hours=00, minutes=00), "backups"
),
}
JOB_STORE = init_webhook_schedule(scheduler=scheduler, job_store=JOB_STORE) JOB_STORE = init_webhook_schedule(scheduler=scheduler, job_store=JOB_STORE)
logger.info(scheduler.print_jobs())
scheduler.start() scheduler.start()

View file

@ -1,11 +1,8 @@
import json
from datetime import date
import requests import requests
from db.database import db from db.database import db
from db.db_setup import create_session from db.db_setup import create_session
from schema.meal import MealOut, MealPlanInDB
from schema.user import GroupInDB from schema.user import GroupInDB
from services.meal_services import get_todays_meal
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
@ -13,23 +10,15 @@ def post_webhooks(group: int, session: Session = None):
session = session if session else create_session() session = session if session else create_session()
group_settings: GroupInDB = db.groups.get(session, group) group_settings: GroupInDB = db.groups.get(session, group)
if group_settings.webhook_enable: if not group_settings.webhook_enable:
today_slug = None return
for mealplan in group_settings.mealplans: todays_recipe = get_todays_meal(session, group)
mealplan: MealPlanInDB
for meal in mealplan.meals:
meal: MealOut
if meal.date == date.today():
today_slug = meal.slug
break
if not today_slug: if not todays_recipe:
return return
todays_meal = db.recipes.get(session, today_slug) for url in group_settings.webhook_urls:
requests.post(url, json=todays_recipe.json())
for url in group_settings.webhook_urls:
requests.post(url, json=todays_meal.json())
session.close() session.close()