diff --git a/README.md b/README.md index 2b630a07a..23223f116 100644 --- a/README.md +++ b/README.md @@ -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 [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 -[product-screenshot]: docs/docs/img/home_screenshot.png +[product-screenshot]: docs/docs/assets/img/home_screenshot.png diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index c8fb6a749..cf73fa8f2 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -36,7 +36,7 @@ services: image: squidfunk/mkdocs-material restart: always ports: - - 9923:8000 + - 9922:8000 volumes: - ./docs:/docs diff --git a/docs/docs/gifs/api-extras.gif b/docs/docs/assets/gifs/api-extras.gif similarity index 100% rename from docs/docs/gifs/api-extras.gif rename to docs/docs/assets/gifs/api-extras.gif diff --git a/docs/docs/gifs/backup-demo-v1.gif b/docs/docs/assets/gifs/backup-demo-v1.gif similarity index 100% rename from docs/docs/gifs/backup-demo-v1.gif rename to docs/docs/assets/gifs/backup-demo-v1.gif diff --git a/docs/docs/gifs/bulk-add-demo.gif b/docs/docs/assets/gifs/bulk-add-demo.gif similarity index 100% rename from docs/docs/gifs/bulk-add-demo.gif rename to docs/docs/assets/gifs/bulk-add-demo.gif diff --git a/docs/docs/gifs/editor-demo.gif b/docs/docs/assets/gifs/editor-demo.gif similarity index 100% rename from docs/docs/gifs/editor-demo.gif rename to docs/docs/assets/gifs/editor-demo.gif diff --git a/docs/docs/gifs/homepage-settings-v1.gif b/docs/docs/assets/gifs/homepage-settings-v1.gif similarity index 100% rename from docs/docs/gifs/homepage-settings-v1.gif rename to docs/docs/assets/gifs/homepage-settings-v1.gif diff --git a/docs/docs/gifs/meal-plan-demo-v2.gif b/docs/docs/assets/gifs/meal-plan-demo-v2.gif similarity index 100% rename from docs/docs/gifs/meal-plan-demo-v2.gif rename to docs/docs/assets/gifs/meal-plan-demo-v2.gif diff --git a/docs/docs/gifs/theme-demo-v2.gif b/docs/docs/assets/gifs/theme-demo-v2.gif similarity index 100% rename from docs/docs/gifs/theme-demo-v2.gif rename to docs/docs/assets/gifs/theme-demo-v2.gif diff --git a/docs/docs/gifs/url-demo.gif b/docs/docs/assets/gifs/url-demo.gif similarity index 100% rename from docs/docs/gifs/url-demo.gif rename to docs/docs/assets/gifs/url-demo.gif diff --git a/docs/docs/assets/img/add-user.png b/docs/docs/assets/img/add-user.png new file mode 100644 index 000000000..02cd1517f Binary files /dev/null and b/docs/docs/assets/img/add-user.png differ diff --git a/docs/docs/img/admin-backup.png b/docs/docs/assets/img/admin-backup.png similarity index 100% rename from docs/docs/img/admin-backup.png rename to docs/docs/assets/img/admin-backup.png diff --git a/docs/docs/img/admin-theme.png b/docs/docs/assets/img/admin-theme.png similarity index 100% rename from docs/docs/img/admin-theme.png rename to docs/docs/assets/img/admin-theme.png diff --git a/docs/docs/img/app_diagram.drawio.svg b/docs/docs/assets/img/app_diagram.drawio.svg similarity index 100% rename from docs/docs/img/app_diagram.drawio.svg rename to docs/docs/assets/img/app_diagram.drawio.svg diff --git a/docs/docs/img/app_diagram.png b/docs/docs/assets/img/app_diagram.png similarity index 100% rename from docs/docs/img/app_diagram.png rename to docs/docs/assets/img/app_diagram.png diff --git a/docs/docs/img/favicon.png b/docs/docs/assets/img/favicon.png similarity index 100% rename from docs/docs/img/favicon.png rename to docs/docs/assets/img/favicon.png diff --git a/docs/docs/assets/img/group-manager.png b/docs/docs/assets/img/group-manager.png new file mode 100644 index 000000000..192da9bb0 Binary files /dev/null and b/docs/docs/assets/img/group-manager.png differ diff --git a/docs/docs/assets/img/home_screenshot.png b/docs/docs/assets/img/home_screenshot.png new file mode 100755 index 000000000..c137d2598 Binary files /dev/null and b/docs/docs/assets/img/home_screenshot.png differ diff --git a/docs/docs/img/ios-shortcut-image.jpg b/docs/docs/assets/img/ios-shortcut-image.jpg similarity index 100% rename from docs/docs/img/ios-shortcut-image.jpg rename to docs/docs/assets/img/ios-shortcut-image.jpg diff --git a/docs/docs/img/iphone-image.png b/docs/docs/assets/img/iphone-image.png similarity index 100% rename from docs/docs/img/iphone-image.png rename to docs/docs/assets/img/iphone-image.png diff --git a/docs/docs/assets/img/sign-up-links.png b/docs/docs/assets/img/sign-up-links.png new file mode 100644 index 000000000..e8e171979 Binary files /dev/null and b/docs/docs/assets/img/sign-up-links.png differ diff --git a/docs/docs/assets/img/site-settings.png b/docs/docs/assets/img/site-settings.png new file mode 100644 index 000000000..a52974068 Binary files /dev/null and b/docs/docs/assets/img/site-settings.png differ diff --git a/docs/docs/stylesheets/custom.css b/docs/docs/assets/stylesheets/custom.css similarity index 100% rename from docs/docs/stylesheets/custom.css rename to docs/docs/assets/stylesheets/custom.css diff --git a/docs/docs/changelog/v0.4.0.md b/docs/docs/changelog/v0.4.0.md index 3ded7999d..eecb60d0c 100644 --- a/docs/docs/changelog/v0.4.0.md +++ b/docs/docs/changelog/v0.4.0.md @@ -1,6 +1,23 @@ # 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 Recette from marmiton.org not fully scrapped - #196 - 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 "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 - Sign Up Links - Admin and User Roles @@ -20,14 +49,14 @@ - Group Management - Create/Edit/Delete Restrictions -#### UI Improvements +### UI Improvements - Completed Redesign of the Admin Panel - Profile Pages - Side Panel Menu - Improved UI for Recipe Search - 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. - Nutrition Information - calories @@ -40,7 +69,7 @@ - "categories" has been migrated to "recipeCategory" to adhere closer to the standard schema - "tool" - a list of tools used for the recipe -#### Behind the Scenes +### Behind the Scenes - Removed CDN dependencies - Database Model Refactoring - File/Folder Name Refactoring diff --git a/docs/docs/contributors/developers-guide/code-contributions.md b/docs/docs/contributors/developers-guide/code-contributions.md index 9d5385e09..6168a7a74 100644 --- a/docs/docs/contributors/developers-guide/code-contributions.md +++ b/docs/docs/contributors/developers-guide/code-contributions.md @@ -1,8 +1,5 @@ # 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. ## We Develop with Github diff --git a/docs/docs/contributors/translating.md b/docs/docs/contributors/translating.md index a53f0d2ad..fa2106cb2 100644 --- a/docs/docs/contributors/translating.md +++ b/docs/docs/contributors/translating.md @@ -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. -## 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. ## 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). + 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. i18n Ally will also show which languages is missing translations. \ No newline at end of file diff --git a/docs/docs/getting-started/api-usage.md b/docs/docs/getting-started/api-usage.md index 95f288f97..4964ae6ed 100644 --- a/docs/docs/getting-started/api-usage.md +++ b/docs/docs/getting-started/api-usage.md @@ -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. -![api-extras-gif](../gifs/api-extras.gif) +![api-extras-gif](../assets/gifs/api-extras.gif) ## Examples diff --git a/docs/docs/getting-started/install.md b/docs/docs/getting-started/install.md index fb5295712..6cc9fcfa1 100644 --- a/docs/docs/getting-started/install.md +++ b/docs/docs/getting-started/install.md @@ -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/) -[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 -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 docker run \ - -e db_type='sqlite' \ - -p 9000:80 \ + -e DB_TYPE='sqlite' \ + -p 9925:80 \ -v `pwd`:'/app/data/' \ hkotel/mealie:latest ``` ## 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 version: "3.1" @@ -30,9 +34,9 @@ services: image: hkotel/mealie:latest restart: always ports: - - 9000:80 + - 9925:80 environment: - db_type: sqlite + DB_TYPE: sqlite TZ: America/Anchorage volumes: - ./mealie/data/:/app/data @@ -41,13 +45,46 @@ services: ## Env Variables -| Variables | default | description | -| ----------- | ------- | ----------------------------------------------------------------------------------- | -| 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_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 | +| Variables | Default | Description | +| ---------------- | -------- | ----------------------------------------------------------------------------------- | +| DB_TYPE | sqlite | The database type to be used. Current Options 'sqlite' | +| 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. | +| 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 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 + } + +} +``` \ No newline at end of file diff --git a/docs/docs/getting-started/introduction.md b/docs/docs/getting-started/introduction.md index 7cf20b8ff..6ce3a9d61 100644 --- a/docs/docs/getting-started/introduction.md +++ b/docs/docs/getting-started/introduction.md @@ -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 - ⬇️ Import recipes from around the web by URL - 📱 Beautiful Mobile Views -- 📆 Create meal plans -- 🛒 Generate shopping lists from meal plans +- 📆 Create Meal Plans +- 🛒 Generate shopping lists from Meal Plans - 🐳 Easy setup with Docker - 🎨 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 - ➕ Plus tons more! - 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. ### 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 diff --git a/docs/docs/getting-started/ios.md b/docs/docs/getting-started/ios.md index cef361cfe..e261eb6ea 100644 --- a/docs/docs/getting-started/ios.md +++ b/docs/docs/getting-started/ios.md @@ -1,5 +1,5 @@ # 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. @@ -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. -![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"} diff --git a/docs/docs/getting-started/meal-planner.md b/docs/docs/getting-started/meal-planner.md index f8dbdc72d..7ea89b35f 100644 --- a/docs/docs/getting-started/meal-planner.md +++ b/docs/docs/getting-started/meal-planner.md @@ -9,5 +9,5 @@ To edit the meal in a meal plan simply select the edit button on the card in the ## 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. -![](../gifs/meal-plan-demo-v2.gif) +![](../assets/gifs/meal-plan-demo-v2.gif) diff --git a/docs/docs/getting-started/recipes.md b/docs/docs/getting-started/recipes.md index 5daa859a2..2ff318133 100644 --- a/docs/docs/getting-started/recipes.md +++ b/docs/docs/getting-started/recipes.md @@ -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. -![](../gifs/url-demo.gif) +![](../assets/gifs/url-demo.gif) ## 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. -![](../gifs/editor-demo.gif) +![](../assets/gifs/editor-demo.gif) ## 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. -![](../gifs/bulk-add-demo.gif) +![](../assets/gifs/bulk-add-demo.gif) ## 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. diff --git a/docs/docs/getting-started/updating.md b/docs/docs/getting-started/updating.md index e0af54aa1..9a200153f 100644 --- a/docs/docs/getting-started/updating.md +++ b/docs/docs/getting-started/updating.md @@ -9,6 +9,10 @@ - Create a Backup and Download from the UI - Upgrade +## Backing Up Your Data + +[See Backups and Restore Section](/site-administration/backups-and-exports/) for details on backing up your data + ## Docker For all setups using Docker the updating process look something like this diff --git a/docs/docs/img/home_screenshot.png b/docs/docs/img/home_screenshot.png deleted file mode 100644 index df879acb0..000000000 Binary files a/docs/docs/img/home_screenshot.png and /dev/null differ diff --git a/docs/docs/overrides/home.html b/docs/docs/overrides/home.html index ad5e33989..de118b70d 100644 --- a/docs/docs/overrides/home.html +++ b/docs/docs/overrides/home.html @@ -225,7 +225,7 @@
- +

@@ -260,7 +260,7 @@ Import Recipes

- 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 recipe scrapper.

@@ -271,7 +271,7 @@ Automatic Backups

- 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 Jinja2 templates

@@ -281,7 +281,7 @@ Rich User Interface -

Use a beautiful and intuitive user interface to create, edit, and delete recipes

+

Use a beautiful and intuitive user interface to create, edit, and delete recipes. Recipe editor supports markdown syntax

@@ -304,7 +304,7 @@ Users

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

@@ -315,7 +315,7 @@ Groups

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

@@ -325,7 +325,7 @@ Webhooks -

Schedule webhooks to send notifications to 3rd party services with todays Meal Plan

+

Schedule webhooks to send notifications to 3rd party services with todays Meal Plan data.

@@ -334,7 +334,7 @@ Open API

-

API Driven application gives you full control of the backend server with interactive documentation

+

API Driven application gives you full control of the backend server with interactive documentation

diff --git a/docs/docs/site-administration/backups-and-exports.md b/docs/docs/site-administration/backups-and-exports.md index 62e1efb13..5ee16d2cd 100644 --- a/docs/docs/site-administration/backups-and-exports.md +++ b/docs/docs/site-administration/backups-and-exports.md @@ -18,7 +18,7 @@ To import a backup it must be in your backups folder. If it is in the backup fol ## Demo -![](../gifs/backup-demo-v1.gif) +![](../assets/gifs/backup-demo-v1.gif) ## 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 diff --git a/docs/docs/site-administration/site-settings.md b/docs/docs/site-administration/site-settings.md index 02b68e96f..5aff8f446 100644 --- a/docs/docs/site-administration/site-settings.md +++ b/docs/docs/site-administration/site-settings.md @@ -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 | | Home Page Sections | Category sections to include on the home page | +![Site Settings Image](../assets/img/site-settings.png) diff --git a/docs/docs/site-administration/user-management.md b/docs/docs/site-administration/user-management.md index 0d8ba5a16..f50103478 100644 --- a/docs/docs/site-administration/user-management.md +++ b/docs/docs/site-administration/user-management.md @@ -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. + **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" 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" - 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 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 - Email - User Group - If they are an Admin + When creating users manually, their password will be set from the default assigned by the ENV variable. ### 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. +![Sign Up Links Image](../assets/img/sign-up-links.png) + !!! tip When a link is used it is automatically removed from the database. ## 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. +![Group Management Panel](../assets/img/group-manager.png) + !!! 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. diff --git a/docs/docs/site-administration/user-settings.md b/docs/docs/site-administration/user-settings.md index e8be770c8..efaa42140 100644 --- a/docs/docs/site-administration/user-settings.md +++ b/docs/docs/site-administration/user-settings.md @@ -14,7 +14,7 @@ In as users profile they are able to ## 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. -![](../gifs/theme-demo-v2.gif) +![](../assets/gifs/theme-demo-v2.gif) !!! 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. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 3a0178b0d..62f8c7453 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -8,7 +8,7 @@ theme: - navigation.sections - navigation.tabs - navigation.tabs.sticky - favicon: img/favicon.png + favicon: assets/img/favicon.png name: material icon: logo: material/silverware-variant @@ -28,7 +28,7 @@ markdown_extensions: - pymdownx.superfences extra_css: - - stylesheets/custom.css + - assets/stylesheets/custom.css repo_url: https://github.com/hay-kot/mealie repo_name: hay-kot/mealie diff --git a/frontend/src/api/mealplan.js b/frontend/src/api/mealplan.js index 0271f51de..4de5d7a85 100644 --- a/frontend/src/api/mealplan.js +++ b/frontend/src/api/mealplan.js @@ -16,6 +16,7 @@ const mealPlanURLs = { export default { async create(postBody) { + console.log(postBody); let response = await apiReq.post(mealPlanURLs.create, postBody); return response; }, diff --git a/frontend/src/components/MealPlan/MealPlanCard.vue b/frontend/src/components/MealPlan/MealPlanCard.vue index 9fffa6e47..f88e27c17 100644 --- a/frontend/src/components/MealPlan/MealPlanCard.vue +++ b/frontend/src/components/MealPlan/MealPlanCard.vue @@ -17,7 +17,9 @@ :src="getImage(meal.slug)" @click="openSearch(index)" > - {{ $d( new Date(meal.date), 'short' ) }} + + {{ $d(new Date(meal.date.split("-")), "short") }} + {{ meal.name }} diff --git a/frontend/src/pages/MealPlan/Planner.vue b/frontend/src/pages/MealPlan/Planner.vue index 474b42a5f..20a5c0ff1 100644 --- a/frontend/src/pages/MealPlan/Planner.vue +++ b/frontend/src/pages/MealPlan/Planner.vue @@ -25,8 +25,8 @@ > - {{ $d(new Date(mealplan.startDate), "short") }} - - {{ $d(new Date(mealplan.endDate), "short") }} + {{ $d(new Date(mealplan.startDate.split("-")), "short") }} - + {{ $d(new Date(mealplan.endDate.split("-")), "short") }} @@ -44,7 +44,7 @@ diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js index 69a8edbc2..94f604e9c 100644 --- a/frontend/src/utils/index.js +++ b/frontend/src/utils/index.js @@ -73,8 +73,8 @@ export default { return `${dow}, ${month} ${day}`; }, getDateAsPythonDate(dateObject) { - const month = dateObject.getMonth() + 1; - const day = dateObject.getDate(); + const month = dateObject.getUTCMonth() + 1; + const day = dateObject.getUTCDate(); const year = dateObject.getFullYear(); return `${year}-${month}-${day}`; diff --git a/mealie/db/database.py b/mealie/db/database.py index 831136a5e..87f9a2786 100644 --- a/mealie/db/database.py +++ b/mealie/db/database.py @@ -5,6 +5,7 @@ from schema.settings import SiteSettings as SiteSettingsSchema from schema.sign_up import SignUpOut from schema.theme import SiteTheme from schema.user import GroupInDB, UserInDB +from sqlalchemy.orm import load_only from sqlalchemy.orm.session import Session from db.db_base import BaseDocument @@ -94,6 +95,27 @@ class _Groups(BaseDocument): self.orm_mode = True 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): def __init__(self) -> None: diff --git a/mealie/db/models/mealplan.py b/mealie/db/models/mealplan.py index fa9915da4..12d164d0a 100644 --- a/mealie/db/models/mealplan.py +++ b/mealie/db/models/mealplan.py @@ -29,7 +29,7 @@ class MealPlanModel(SqlAlchemyBase, BaseMixins): uid = sa.Column(sa.Integer, primary_key=True, unique=True) #! Probably Bad? startDate = 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 = orm.relationship("Group", back_populates="mealplans") @@ -42,7 +42,6 @@ class MealPlanModel(SqlAlchemyBase, BaseMixins): self.meals = [Meal(**meal) for meal in meals] def update(self, session, startDate, endDate, meals, uid, group) -> None: - MealPlanModel._sql_remove_list(session, [Meal], uid) self.__init__( startDate=startDate, diff --git a/mealie/routes/mealplans/crud.py b/mealie/routes/mealplans/crud.py index 7a3710708..2d2255e41 100644 --- a/mealie/routes/mealplans/crud.py +++ b/mealie/routes/mealplans/crud.py @@ -1,3 +1,5 @@ +import datetime + from db.database import db from db.db_setup import generate_session from fastapi import APIRouter, Depends @@ -17,9 +19,8 @@ def get_all_meals( session: Session = Depends(generate_session), ): """ Returns a list of all available Meal Plan """ - print(current_user.group) - group_entry: GroupInDB = db.groups.get(session, current_user.group, "name") - return group_entry.mealplans + + return db.groups.get_meals(session, current_user.group) @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) -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 """ - 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"]) -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. 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 diff --git a/mealie/services/backups/exports.py b/mealie/services/backups/exports.py index c5ee04aa6..49b23f2ad 100644 --- a/mealie/services/backups/exports.py +++ b/mealie/services/backups/exports.py @@ -2,19 +2,21 @@ import json import shutil from datetime import datetime from pathlib import Path +from typing import Union from core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR, TEMPLATE_DIR from db.database import db from db.db_setup import create_session from fastapi.logger import logger from jinja2 import Template +from pydantic.main import BaseModel from schema.recipe import Recipe 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 - 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 @@ -27,14 +29,9 @@ class ExportDatabase: else: export_tag = datetime.now().strftime("%Y-%b-%d") - self.session = session self.main_dir = TEMP_DIR.joinpath(export_tag) 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.mealplans_dir = self.main_dir.joinpath("mealplans") try: self.templates = [TEMPLATE_DIR.joinpath(x) for x in templates] @@ -45,71 +42,50 @@ class ExportDatabase: required_dirs = [ self.main_dir, self.img_dir, - self.recipe_dir, - self.themes_dir, - self.settings_dir, self.templates_dir, - self.mealplans_dir, ] for dir in required_dirs: dir.mkdir(parents=True, exist_ok=True) - def export_recipes(self): - 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): + def export_templates(self, recipe_list: list[BaseModel]): 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: template = Template(f.read()) - filename = recipe_data.name + template_path.suffix - out_file = self.templates_dir.joinpath(filename) + for recipe in recipe_list: + 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: - f.write(content) + with open(out_file, "w") as f: + f.write(content) def export_images(self): for file in IMG_DIR.iterdir(): shutil.copy(file, self.img_dir.joinpath(file.name)) - def export_settings(self): - all_settings = db.settings.get(self.session, "main") - out_file = self.settings_dir.joinpath("settings.json") - ExportDatabase._write_json_file(all_settings, out_file) + def export_items(self, items: list[BaseModel], folder_name: str, export_list=True): + items = [x.dict() for x in items] + out_dir = self.main_dir.joinpath(folder_name) + out_dir.mkdir(parents=True, exist_ok=True) - def export_themes(self): - all_themes = db.themes.get_all(self.session) - if all_themes: - out_file = self.themes_dir.joinpath("themes.json") - ExportDatabase._write_json_file(all_themes, out_file) - - def export_meals(self): - #! Problem Parseing Datetime Objects... May come back to this - 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) + if export_list: + ExportDatabase._write_json_file( + items, out_dir.joinpath(f"{folder_name}.json") + ) + else: + for item in items: + ExportDatabase._write_json_file( + item, out_dir.joinpath(f"{item.get('name')}.json") + ) @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) with open(out_file, "w") as f: @@ -131,19 +107,32 @@ def backup_all( export_recipes=True, export_settings=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: - 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() if export_settings: - db_export.export_settings() + all_settings = db.settings.get_all(session) + db_export.export_items(all_settings, "settings") if export_themes: - db_export.export_themes() - # db_export.export_meals() + all_themes = db.themes.get_all(session) + db_export.export_items(all_themes, "themes") return db_export.finish_export() diff --git a/mealie/services/meal_services.py b/mealie/services/meal_services.py index 2d2851089..5d97329b6 100644 --- a/mealie/services/meal_services.py +++ b/mealie/services/meal_services.py @@ -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.db_setup import create_session +from pydantic.tools import T from schema.meal import MealIn, MealOut, MealPlanIn, MealPlanInDB, MealPlanProcessed from schema.recipe import Recipe +from schema.user import GroupInDB from sqlalchemy.orm.session import Session def process_meals(session: Session, meal_plan_base: MealPlanIn) -> MealPlanProcessed: 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 try: 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): - meal_plan: MealPlanInDB = db.groups.get(session, limit=1, order_by="startDate") +def get_todays_meal(session: Session, group: Union[int, GroupInDB]) -> Recipe: + """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: - meal: MealOut - if meal.date == date.today(): - return meal.slug + Args: + session (Session): SqlAlchemy Session + group (Union[int, GroupInDB]): Either the id of the group or the GroupInDB Object + + 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 diff --git a/mealie/services/scheduler/scheduled_jobs.py b/mealie/services/scheduler/scheduled_jobs.py index c8d6cdaf5..d377bdf79 100644 --- a/mealie/services/scheduler/scheduled_jobs.py +++ b/mealie/services/scheduler/scheduled_jobs.py @@ -55,18 +55,6 @@ class ScheduledFunction: 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): session = create_session() @@ -88,12 +76,19 @@ def init_webhook_schedule(scheduler, job_store: dict): ) session.close() - logger.info("Init Webhook Schedule \n", scheduler.print_jobs()) 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) - +logger.info(scheduler.print_jobs()) scheduler.start() diff --git a/mealie/utils/post_webhooks.py b/mealie/utils/post_webhooks.py index fdf4d6482..135a38d56 100644 --- a/mealie/utils/post_webhooks.py +++ b/mealie/utils/post_webhooks.py @@ -1,11 +1,8 @@ -import json -from datetime import date - import requests from db.database import db from db.db_setup import create_session -from schema.meal import MealOut, MealPlanInDB from schema.user import GroupInDB +from services.meal_services import get_todays_meal 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() group_settings: GroupInDB = db.groups.get(session, group) - if group_settings.webhook_enable: - today_slug = None + if not group_settings.webhook_enable: + return - for mealplan in group_settings.mealplans: - mealplan: MealPlanInDB - for meal in mealplan.meals: - meal: MealOut - if meal.date == date.today(): - today_slug = meal.slug - break + todays_recipe = get_todays_meal(session, group) - if not today_slug: - return + if not todays_recipe: + return - todays_meal = db.recipes.get(session, today_slug) - - for url in group_settings.webhook_urls: - requests.post(url, json=todays_meal.json()) + for url in group_settings.webhook_urls: + requests.post(url, json=todays_recipe.json()) session.close()