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>
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 14 MiB After Width: | Height: | Size: 14 MiB |
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 2.9 MiB After Width: | Height: | Size: 2.9 MiB |
Before Width: | Height: | Size: 30 MiB After Width: | Height: | Size: 30 MiB |
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
Before Width: | Height: | Size: 8.4 MiB After Width: | Height: | Size: 8.4 MiB |
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 35 MiB After Width: | Height: | Size: 35 MiB |
BIN
docs/docs/assets/img/add-user.png
Normal file
After Width: | Height: | Size: 214 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 138 KiB |
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 7 KiB |
BIN
docs/docs/assets/img/group-manager.png
Normal file
After Width: | Height: | Size: 619 KiB |
BIN
docs/docs/assets/img/home_screenshot.png
Executable file
After Width: | Height: | Size: 533 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 350 KiB After Width: | Height: | Size: 350 KiB |
BIN
docs/docs/assets/img/sign-up-links.png
Normal file
After Width: | Height: | Size: 482 KiB |
BIN
docs/docs/assets/img/site-settings.png
Normal file
After Width: | Height: | Size: 175 KiB |
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
|
@ -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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Using iOS Shortcuts with Mealie
|
# Using iOS Shortcuts with Mealie
|
||||||
{: align=right style="height:400px;width:400px"}
|
{: 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.
|
||||||
|
|
||||||
|
|
||||||
{: align=right style="height:500;width:400px"}
|
{: align=right style="height:500;width:400px"}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## 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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 1.7 MiB |
|
@ -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>
|
||||||
|
|
|
@ -18,7 +18,7 @@ To import a backup it must be in your backups folder. If it is in the backup fol
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 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
|
||||||
|
|
|
@ -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 |
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
{: 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
!!! 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
!!! 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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
!!! 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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
},
|
},
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}`;
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|