mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 14:33:33 -07:00
Feature/authentication (#213)
* 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 * 📦 Proper Package + Black Config * formatting * delete old files * fix ci * fix failing builds * package/makefile docs update * add docs server to tasks Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
parent
40c12b6c37
commit
25ec3a66f0
190 changed files with 17972 additions and 3679 deletions
4
.github/workflows/pytest.yml
vendored
4
.github/workflows/pytest.yml
vendored
|
@ -31,7 +31,7 @@ jobs:
|
|||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
# #----------------------------------------------
|
||||
# # load cached venv if cache exists
|
||||
# # load cached venv if cache exists #! This Breaks Stuff
|
||||
# #----------------------------------------------
|
||||
# - name: Load cached venv
|
||||
# id: cached-poetry-dependencies
|
||||
|
@ -51,4 +51,4 @@ jobs:
|
|||
- name: Run tests
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
pytest mealie/tests/
|
||||
pytest
|
||||
|
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -5,13 +5,12 @@
|
|||
"python.linting.enabled": true,
|
||||
"python.autoComplete.extraPaths": ["mealie", "mealie/mealie"],
|
||||
"python.analysis.extraPaths": ["mealie", "mealie/mealie"],
|
||||
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.nosetestsEnabled": false,
|
||||
"python.testing.pytestEnabled": true,
|
||||
"python.testing.autoTestDiscoverOnSaveEnabled": false,
|
||||
"python.testing.pytestArgs": ["tests"],
|
||||
"cSpell.enableFiletypes": ["!javascript", "!python"],
|
||||
"python.testing.pytestArgs": ["mealie"],
|
||||
"i18n-ally.localesPaths": "frontend/src/locales/messages",
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.enabledFrameworks": ["vue"],
|
||||
|
|
37
.vscode/tasks.json
vendored
37
.vscode/tasks.json
vendored
|
@ -3,12 +3,10 @@
|
|||
"tasks": [
|
||||
{
|
||||
"label": "DEV: Build and Start Docker Compose",
|
||||
"command": "./dev/scripts/docker-compose.dev.sh",
|
||||
"command": "make docker-dev",
|
||||
"type": "shell",
|
||||
"args": [],
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
],
|
||||
"problemMatcher": ["$tsc"],
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
|
@ -16,26 +14,18 @@
|
|||
},
|
||||
{
|
||||
"label": "Production: Build and Start Docker Compose",
|
||||
"command": "./dev/scripts/docker-compose.sh",
|
||||
"command": "make docker-prod",
|
||||
"type": "shell",
|
||||
"args": [],
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
],
|
||||
"problemMatcher": ["$tsc"],
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
"group": "test"
|
||||
},
|
||||
{
|
||||
"label": "Dev: Start local Backend",
|
||||
"command": "../${config:python.pythonPath}",
|
||||
"args": [
|
||||
"app.py"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/mealie/"
|
||||
},
|
||||
"label": "Dev: Start Backend",
|
||||
"command": "make backend",
|
||||
"type": "shell",
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
|
@ -44,12 +34,19 @@
|
|||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Dev: Start local Frontend",
|
||||
"command": "npm run serve",
|
||||
"label": "Dev: Start Frontend",
|
||||
"command": "make vue",
|
||||
"type": "shell",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/frontend/"
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"group": "groupA"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Dev: Start Docs Server",
|
||||
"command": "make mdocs",
|
||||
"type": "shell",
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"group": "groupA"
|
||||
|
|
10
Dockerfile
10
Dockerfile
|
@ -10,10 +10,11 @@ FROM python:3.9-alpine
|
|||
RUN apk add --no-cache libxml2-dev libxslt-dev libxml2 caddy libffi-dev
|
||||
ENV ENV prod
|
||||
EXPOSE 80
|
||||
WORKDIR /app
|
||||
WORKDIR /app/
|
||||
|
||||
COPY ./pyproject.toml /app/
|
||||
|
||||
|
||||
RUN apk add --update --no-cache --virtual .build-deps \
|
||||
curl \
|
||||
g++ \
|
||||
|
@ -29,13 +30,12 @@ RUN apk add --update --no-cache --virtual .build-deps \
|
|||
apk --purge del .build-deps
|
||||
|
||||
|
||||
COPY ./mealie /app
|
||||
COPY ./mealie /app/mealie
|
||||
COPY ./Caddyfile /app
|
||||
COPY ./app_data/templates /app/data/templates
|
||||
RUN rm -rf /app/tests /app/.temp
|
||||
COPY --from=build-stage /app/dist /app/dist
|
||||
|
||||
VOLUME [ "/app/data/" ]
|
||||
RUN chmod +x /app/run.sh
|
||||
CMD /app/run.sh
|
||||
RUN chmod +x /app/mealie/run.sh
|
||||
CMD /app/mealie/run.sh
|
||||
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
FROM python:3
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y python-pip python-dev
|
||||
|
||||
# Install Poetry
|
||||
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | POETRY_HOME=/opt/poetry python && \
|
||||
cd /usr/local/bin && \
|
||||
ln -s /opt/poetry/bin/poetry && \
|
||||
poetry config virtualenvs.create false
|
||||
|
||||
# Copy poetry.lock* in case it doesn't exist in the repo
|
||||
COPY ./pyproject.toml ./poetry.lock* /app/
|
||||
|
||||
WORKDIR /app
|
||||
RUN poetry install
|
||||
|
||||
RUN poetry install --no-root
|
||||
COPY ./mealie /app/mealie
|
||||
|
||||
COPY ./mealie /app
|
||||
|
||||
ENTRYPOINT [ "python" ]
|
||||
|
||||
CMD [ "app.py" ]
|
||||
CMD ["uvicorn", "mealie.app:app", "--host", "0.0.0.0", "--port", "9000"]
|
|
@ -1,19 +1,17 @@
|
|||
|
||||
|
||||

|
||||

|
||||
|
||||
# {{ recipe.name }}
|
||||
{{ recipe.description }}
|
||||
|
||||
## Ingredients
|
||||
{% for ingredient in recipe.recipeIngredient %}
|
||||
- [ ] {{ ingredient }}
|
||||
{% endfor %}
|
||||
{% for ingredient in recipe.recipeIngredient %}
|
||||
- [ ] {{ ingredient }} {% endfor %}
|
||||
|
||||
## Instructions
|
||||
{% for step in recipe.recipeInstructions %}
|
||||
- [ ] {{ step.text }}
|
||||
{% endfor %}
|
||||
{% for step in recipe.recipeInstructions %}
|
||||
- [ ] {{ step.text }} {% endfor %}
|
||||
|
||||
{% for note in recipe.notes %}
|
||||
**{{ note.title }}:** {{ note.text }}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
# Getting A Developer Instance Started
|
||||
For the best experience developing I recommend using docker. I've used both WSL2 and Ubuntu to develop mealie and have had no issues with cross compatibility with docker. 2 Scripts are available along ith docker-compose files to make development instances easier. After cloning the repo you can set the scripts in /dev/scripts/ as executable and then use VSCode tasks to execute the scripts or execute them from the CLI.
|
||||
|
||||
`docker-compose.dev.sh` Will spin up a development stack with hot-reloading enabled.
|
||||
`docker-compose.sh` Will spin up a production version of the stack.
|
||||
|
||||
After the stack is running navigate to the [admin page localhost:9090/settings/site](http://localhost:9090/settings/site). On the Backups and Exports section import the backup labeled dev_sample_data_{DATE}.zip. This will give you some recipe data to work with.
|
||||
|
||||
Once you're up and running you should be able to make changes and see them reflected on the frontend/backend. If you're not sure what to work on you can check:
|
||||
|
||||
- The Todo's below.
|
||||
- The [Development Road Map](https://hay-kot.github.io/mealie/2.0%20-%20roadmap/)
|
||||
- The [Current Open Issues](https://github.com/hay-kot/mealie/issues)
|
||||
|
||||
Don't forget to [join the Discord](https://discord.gg/R6QDyJgbD2)!
|
||||
|
||||
# Todo's
|
||||
|
||||
Test
|
||||
- [ ] Image Upload Test
|
||||
- [ ] Rename and Upload Image Test
|
||||
- [x] Chowdown Migration End Point Test
|
||||
|
||||
Frontend
|
||||
- [ ] No Meal Today Page instead of Null
|
||||
- [ ] Recipe Print Page
|
||||
- [ ] Recipe Editor Data Validation Client Side
|
||||
- [ ] Organize Home Page my Category, ideally user selectable.
|
||||
- [ ] Advanced Search Page, draft started
|
||||
- [ ] Filter by Category
|
||||
- [ ] Filter by Tags
|
||||
- [ ] Search Bar Results Redesign
|
||||
|
||||
Backend
|
||||
- [ ] Database Import
|
||||
- [x] Recipes
|
||||
- [x] Images
|
||||
- [ ] Meal Plans
|
||||
- [x] Settings
|
||||
- [x] Themes
|
||||
- [ ] Remove Print / Debug Code
|
||||
- [ ] Support how to sections and how to steps
|
||||
- [ ] Recipe request by category/tags
|
||||
|
||||
SQL
|
||||
- [ ] Setup Database Migrations
|
||||
|
||||
# Draft Changelog
|
|
@ -1 +0,0 @@
|
|||
http://www.cookingforkeeps.com/2013/02/05/blue-cheese-stuffed-turkey-meatballs-with-raspberry-balsamic-glaze-2/
|
|
@ -1,32 +0,0 @@
|
|||
import requests
|
||||
import json
|
||||
|
||||
POST_URL = "http://localhost:9921/api/recipe/create-url"
|
||||
URL_LIST = [
|
||||
"https://www.bonappetit.com/recipe/hallacas"
|
||||
"https://www.bonappetit.com/recipe/oat-and-pecan-brittle-cookies",
|
||||
"https://www.bonappetit.com/recipe/tequila-beer-and-citrus-cocktail",
|
||||
"https://www.bonappetit.com/recipe/corn-and-crab-beignets-with-yaji-aioli",
|
||||
"https://www.bonappetit.com/recipe/nan-e-berenji",
|
||||
"https://www.bonappetit.com/recipe/ginger-citrus-cookies",
|
||||
"https://www.bonappetit.com/recipe/chocolate-pizzettes-cookies",
|
||||
"https://www.bonappetit.com/recipe/swedish-glogg",
|
||||
"https://www.bonappetit.com/recipe/roasted-beets-with-dukkah-and-sage",
|
||||
"https://www.bonappetit.com/recipe/collard-greens-salad-with-pickled-fennel-and-coconut"
|
||||
"https://www.bonappetit.com/recipe/sparkling-wine-cocktail",
|
||||
"https://www.bonappetit.com/recipe/pretzel-and-potato-chip-moon-pies",
|
||||
"https://www.bonappetit.com/recipe/coffee-hazlenut-biscotti",
|
||||
"https://www.bonappetit.com/recipe/curry-cauliflower-rice",
|
||||
"https://www.bonappetit.com/recipe/life-of-the-party-layer-cake",
|
||||
"https://www.bonappetit.com/recipe/marranitos-enfiestados",
|
||||
]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for url in URL_LIST:
|
||||
data = {"url": url}
|
||||
data = json.dumps(data)
|
||||
try:
|
||||
response = requests.post(POST_URL, data)
|
||||
except:
|
||||
continue
|
|
@ -1,39 +0,0 @@
|
|||
import json
|
||||
|
||||
import requests
|
||||
|
||||
POST_URL = "http://localhost:9921/api/site-settings/themes/create/"
|
||||
GET_URL = "http://localhost:9921/api/site-settings/themes/"
|
||||
|
||||
SITE_SETTINGS = [
|
||||
{
|
||||
"name": "default",
|
||||
"colors": {
|
||||
"primary": "#E58325",
|
||||
"accent": "#00457A",
|
||||
"secondary": "#973542",
|
||||
"success": "#5AB1BB",
|
||||
"info": "#FFFD99",
|
||||
"warning": "#FF4081",
|
||||
"error": "#EF5350",
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "purple",
|
||||
"colors": {
|
||||
"accent": "#4527A0",
|
||||
"primary": "#FF4081",
|
||||
"secondary": "#26C6DA",
|
||||
"success": "#4CAF50",
|
||||
"info": "#2196F3",
|
||||
"warning": "#FB8C00",
|
||||
"error": "#FF5252",
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
if __name__ == "__main__":
|
||||
for theme in SITE_SETTINGS:
|
||||
data = json.dumps(theme)
|
||||
response = requests.post(POST_URL, data)
|
||||
response = requests.get(GET_URL)
|
|
@ -1 +0,0 @@
|
|||
docker-compose -f docker-compose.dev.yml -p dev-mealie up --build
|
|
@ -1 +0,0 @@
|
|||
docker-compose -p mealie up --build
|
|
@ -2,19 +2,19 @@
|
|||
version: "3.1"
|
||||
services:
|
||||
# Vue Frontend
|
||||
mealie-frontend:
|
||||
image: mealie-frontend:dev
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: frontend.Dockerfile
|
||||
restart: always
|
||||
ports:
|
||||
- 9920:8080
|
||||
environment:
|
||||
VUE_APP_API_BASE_URL: "http://mealie-api:9000"
|
||||
volumes:
|
||||
- ./frontend/:/app
|
||||
- /app/node_modules
|
||||
# mealie-frontend:
|
||||
# image: mealie-frontend:dev
|
||||
# build:
|
||||
# context: ./frontend
|
||||
# dockerfile: frontend.Dockerfile
|
||||
# restart: always
|
||||
# ports:
|
||||
# - 9920:8080
|
||||
# environment:
|
||||
# VUE_APP_API_BASE_URL: "http://mealie-api:9000"
|
||||
# volumes:
|
||||
# - ./frontend/:/app
|
||||
# - /app/node_modules
|
||||
|
||||
# Fast API
|
||||
mealie-api:
|
||||
|
@ -32,11 +32,11 @@ services:
|
|||
- ./app_data:/app_data
|
||||
- ./mealie:/app
|
||||
|
||||
mealie-docs:
|
||||
image: squidfunk/mkdocs-material
|
||||
restart: always
|
||||
ports:
|
||||
- 9922:8000
|
||||
volumes:
|
||||
- ./docs:/docs
|
||||
# mealie-docs:
|
||||
# image: squidfunk/mkdocs-material
|
||||
# restart: always
|
||||
# ports:
|
||||
# - 9922:8000
|
||||
# volumes:
|
||||
# - ./docs:/docs
|
||||
|
||||
|
|
|
@ -73,3 +73,7 @@
|
|||
- Removed CDN dependencies
|
||||
- Database Model Refactoring
|
||||
- File/Folder Name Refactoring
|
||||
- Development is now easier with a makefile!
|
||||
- Mealie is now a proper package using poetry
|
||||
- Test refactoring
|
||||
- Test Coverage 75%
|
||||
|
|
|
@ -3,27 +3,36 @@
|
|||
After reading through the [Code Contributions Guide](https://hay-kot.github.io/mealie/contributors/developers-guide/code-contributions/) and forking the repo you can start working. This project is developed with :whale: docker and as such you will be greatly aided by using docker for development. It's not necessary but it is helpful.
|
||||
|
||||
## With Docker
|
||||
`cd` into frontend directory and run `npm install` to install the node modules.
|
||||
Prerequisites
|
||||
|
||||
There are 2 scripts to help set up the docker containers in dev/scripts/.
|
||||
|
||||
`docker-compose.dev.sh` - Will spin up a docker development server
|
||||
`docker-compose.sh` - Will spin up a docker production server
|
||||
|
||||
There are VSCode tasks created in the .vscode folder. You can use these to quickly execute the scripts above using the command palette.
|
||||
- Docker
|
||||
- docker-compose
|
||||
|
||||
You can easily start the development stack by running `make docker-dev` in the root of the project directory. This will run and build the docker-compose.dev.yml file.
|
||||
|
||||
## Without Docker
|
||||
Prerequisites
|
||||
|
||||
- Python 3.8+
|
||||
- Python 3.9+
|
||||
- Poetry
|
||||
- Nodejs
|
||||
- npm
|
||||
|
||||
change directories into the mealie directory and run poetry install. cd into the frontend directory and run npm install. After installing dependencies, you can use vscode tasks to run the front and backend server. Use the command pallette to access the tasks.
|
||||
Once the prerequisites are installed you can cd into the project base directory and run `make setup` to install the python and node dependencies. Once that is complete you can run `make backend` and `make vue` to start the backend and frontend servers.
|
||||
|
||||
## Make File Reference
|
||||
`make setup` installs python and node dependencies
|
||||
|
||||
`make backend` Starts the backend server on port `9000`
|
||||
|
||||
`make vue` Starts the frontend server on port `8080`
|
||||
|
||||
`make mdocs` Starts the documentation server on port `8000`
|
||||
|
||||
`make docker-dev` Builds docker-compose.dev.yml
|
||||
|
||||
`make docker-prod` Builds docker-compose.yml to test for production
|
||||
|
||||
Alternatively you can run `npm run serve` in the frontend directory and `python app.py` in the mealie directory to get everything up and running for development.
|
||||
|
||||
## Trouble Shooting
|
||||
|
||||
|
|
|
@ -12,15 +12,15 @@ Mealie is a self hosted recipe manager and meal planner with a RestAPI backend a
|
|||
|
||||
|
||||
## Key Features
|
||||
- 🔍 Powerful fuzzy search
|
||||
- 🔍 Fuzzy search
|
||||
- 🏷️ 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
|
||||
- 📆 Create Meal Plans
|
||||
- 🛒 Generate shopping lists from Meal Plans
|
||||
- 🛒 Generate shopping lists
|
||||
- 🐳 Easy setup with Docker
|
||||
- 🎨 Customize your interface with color themes layouts
|
||||
- ✉️ Export all your data in any format with Jinja2 Templates, with easy data restoration from the UI.
|
||||
- 💾 Export all your data in any format with Jinja2 Templates, with easy data restoration from the user interface.
|
||||
- 🌍 localized in many languages
|
||||
- ➕ Plus tons more!
|
||||
- Flexible API
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM node:lts-alpine
|
||||
FROM node:latest
|
||||
|
||||
# # install simple http server for serving static content
|
||||
# RUN npm install -g http-server
|
||||
|
|
20316
frontend/package-lock.json
generated
20316
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -9,7 +9,7 @@
|
|||
"i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@adapttive/vue-markdown": "^3.0.3",
|
||||
"@adapttive/vue-markdown": "^4.0.1",
|
||||
"@smartweb/vue-flash-message": "^0.6.10",
|
||||
"axios": "^0.21.1",
|
||||
"core-js": "^3.9.1",
|
||||
|
|
24
makefile
Normal file
24
makefile
Normal file
|
@ -0,0 +1,24 @@
|
|||
setup:
|
||||
poetry install && \
|
||||
cd frontend && \
|
||||
npm install && \
|
||||
cd ..
|
||||
|
||||
backend:
|
||||
source ./.venv/bin/activate && python mealie/app.py
|
||||
|
||||
vue:
|
||||
cd frontend && npm run serve
|
||||
|
||||
mdocs:
|
||||
source ./.venv/bin/activate && \
|
||||
cd docs && \
|
||||
mkdocs serve
|
||||
|
||||
docker-dev:
|
||||
docker-compose -f docker-compose.dev.yml -p dev-mealie up --build
|
||||
|
||||
docker-prod:
|
||||
docker-compose -p mealie up --build -d
|
||||
|
||||
|
|
@ -3,25 +3,25 @@ from fastapi import FastAPI
|
|||
from fastapi.logger import logger
|
||||
|
||||
# import utils.startup as startup
|
||||
from core.config import APP_VERSION, PORT, docs_url, redoc_url
|
||||
from db.db_setup import sql_exists
|
||||
from db.init_db import init_db
|
||||
from routes import (
|
||||
from mealie.core.config import APP_VERSION, PORT, docs_url, redoc_url
|
||||
from mealie.db.db_setup import sql_exists
|
||||
from mealie.db.init_db import init_db
|
||||
from mealie.routes import (
|
||||
backup_routes,
|
||||
debug_routes,
|
||||
migration_routes,
|
||||
setting_routes,
|
||||
theme_routes,
|
||||
)
|
||||
from routes.groups import groups
|
||||
from routes.mealplans import mealplans
|
||||
from routes.recipe import (
|
||||
from mealie.routes.groups import groups
|
||||
from mealie.routes.mealplans import mealplans
|
||||
from mealie.routes.recipe import (
|
||||
all_recipe_routes,
|
||||
category_routes,
|
||||
recipe_crud_routes,
|
||||
tag_routes,
|
||||
)
|
||||
from routes.users import users
|
||||
from mealie.routes.users import users
|
||||
|
||||
app = FastAPI(
|
||||
title="Mealie",
|
||||
|
@ -37,7 +37,7 @@ def data_base_first_run():
|
|||
|
||||
|
||||
def start_scheduler():
|
||||
import services.scheduler.scheduled_jobs
|
||||
import mealie.services.scheduler.scheduled_jobs
|
||||
|
||||
|
||||
def api_routers():
|
||||
|
@ -68,8 +68,8 @@ if not sql_exists:
|
|||
api_routers()
|
||||
start_scheduler()
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("-----SYSTEM STARTUP-----")
|
||||
|
||||
def main():
|
||||
|
||||
uvicorn.run(
|
||||
"app:app",
|
||||
|
@ -81,3 +81,8 @@ if __name__ == "__main__":
|
|||
workers=1,
|
||||
forwarded_allow_ips="*",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("-----SYSTEM STARTUP-----")
|
||||
main()
|
||||
|
|
|
@ -78,9 +78,7 @@ if DATABASE_TYPE == "sqlite":
|
|||
SQLITE_FILE = SQLITE_DIR.joinpath(f"mealie_{DB_VERSION}.sqlite")
|
||||
|
||||
else:
|
||||
raise Exception(
|
||||
"Unable to determine database type. Acceptible options are 'sqlite' "
|
||||
)
|
||||
raise Exception("Unable to determine database type. Acceptible options are 'sqlite' ")
|
||||
|
||||
# Mongo Database
|
||||
MEALIE_DB_NAME = os.getenv("mealie_db_name", "mealie")
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
from schema.category import RecipeCategoryResponse, RecipeTagResponse
|
||||
from schema.meal import MealPlanInDB
|
||||
from schema.recipe import Recipe
|
||||
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 mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse
|
||||
from mealie.schema.meal import MealPlanInDB
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.schema.settings import SiteSettings as SiteSettingsSchema
|
||||
from mealie.schema.sign_up import SignUpOut
|
||||
from mealie.schema.theme import SiteTheme
|
||||
from mealie.schema.user import GroupInDB, UserInDB
|
||||
from sqlalchemy.orm import load_only
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from db.db_base import BaseDocument
|
||||
from db.models.group import Group
|
||||
from db.models.mealplan import MealPlanModel
|
||||
from db.models.recipe.recipe import Category, RecipeModel, Tag
|
||||
from db.models.settings import SiteSettings
|
||||
from db.models.sign_up import SignUp
|
||||
from db.models.theme import SiteThemeModel
|
||||
from db.models.users import User
|
||||
from mealie.db.db_base import BaseDocument
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.mealplan import MealPlanModel
|
||||
from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag
|
||||
from mealie.db.models.settings import SiteSettings
|
||||
from mealie.db.models.sign_up import SignUp
|
||||
from mealie.db.models.theme import SiteThemeModel
|
||||
from mealie.db.models.users import User
|
||||
|
||||
|
||||
class _Recipes(BaseDocument):
|
||||
|
@ -95,9 +95,7 @@ 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]:
|
||||
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:
|
||||
|
@ -108,13 +106,11 @@ class _Groups(BaseDocument):
|
|||
Returns:
|
||||
list[MealPlanInDB]: [description]
|
||||
"""
|
||||
group: GroupInDB = (
|
||||
session.query(self.sql_model)
|
||||
.filter_by(**{match_key: match_value})
|
||||
.one_or_none()
|
||||
)
|
||||
group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none()
|
||||
|
||||
return sorted(group.mealplans, key=lambda mealplan: mealplan.startDate)
|
||||
# Potentially not needed? column is sorted by SqlAlchemy based on startDate
|
||||
# return sorted(group.mealplans, key=lambda mealplan: mealplan.startDate)
|
||||
return group.mealplans
|
||||
|
||||
|
||||
class _SignUps(BaseDocument):
|
||||
|
|
|
@ -4,7 +4,7 @@ from pydantic import BaseModel
|
|||
from sqlalchemy.orm import load_only
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class BaseDocument:
|
||||
|
@ -16,15 +16,10 @@ class BaseDocument:
|
|||
self.schema: BaseModel
|
||||
|
||||
# TODO: Improve Get All Query Functionality
|
||||
def get_all(
|
||||
self, session: Session, limit: int = None, order_by: str = None
|
||||
) -> List[dict]:
|
||||
def get_all(self, session: Session, limit: int = None, order_by: str = None) -> List[dict]:
|
||||
|
||||
if self.orm_mode:
|
||||
return [
|
||||
self.schema.from_orm(x)
|
||||
for x in session.query(self.sql_model).limit(limit).all()
|
||||
]
|
||||
return [self.schema.from_orm(x) for x in session.query(self.sql_model).limit(limit).all()]
|
||||
|
||||
list = [x.dict() for x in session.query(self.sql_model).limit(limit).all()]
|
||||
|
||||
|
@ -33,9 +28,7 @@ class BaseDocument:
|
|||
|
||||
return list
|
||||
|
||||
def get_all_limit_columns(
|
||||
self, session: Session, fields: List[str], limit: int = None
|
||||
) -> List[SqlAlchemyBase]:
|
||||
def get_all_limit_columns(self, session: Session, fields: List[str], limit: int = None) -> List[SqlAlchemyBase]:
|
||||
"""Queries the database for the selected model. Restricts return responses to the
|
||||
keys specified under "fields"
|
||||
|
||||
|
@ -47,9 +40,7 @@ class BaseDocument:
|
|||
Returns:
|
||||
list[SqlAlchemyBase]: Returns a list of ORM objects
|
||||
"""
|
||||
results = (
|
||||
session.query(self.sql_model).options(load_only(*fields)).limit(limit).all()
|
||||
)
|
||||
results = session.query(self.sql_model).options(load_only(*fields)).limit(limit).all()
|
||||
|
||||
return results
|
||||
|
||||
|
@ -63,15 +54,11 @@ class BaseDocument:
|
|||
Returns:
|
||||
list[str]:
|
||||
"""
|
||||
results = session.query(self.sql_model).options(
|
||||
load_only(str(self.primary_key))
|
||||
)
|
||||
results = session.query(self.sql_model).options(load_only(str(self.primary_key)))
|
||||
results_as_dict = [x.dict() for x in results]
|
||||
return [x.get(self.primary_key) for x in results_as_dict]
|
||||
|
||||
def _query_one(
|
||||
self, session: Session, match_value: str, match_key: str = None
|
||||
) -> SqlAlchemyBase:
|
||||
def _query_one(self, session: Session, match_value: str, match_key: str = None) -> SqlAlchemyBase:
|
||||
"""Query the sql database for one item an return the sql alchemy model
|
||||
object. If no match key is provided the primary_key attribute will be used.
|
||||
|
||||
|
@ -85,15 +72,11 @@ class BaseDocument:
|
|||
if match_key == None:
|
||||
match_key = self.primary_key
|
||||
|
||||
result = (
|
||||
session.query(self.sql_model).filter_by(**{match_key: match_value}).one()
|
||||
)
|
||||
result = session.query(self.sql_model).filter_by(**{match_key: match_value}).one()
|
||||
|
||||
return result
|
||||
|
||||
def get(
|
||||
self, session: Session, match_value: str, match_key: str = None, limit=1
|
||||
) -> dict or List[dict]:
|
||||
def get(self, session: Session, match_value: str, match_key: str = None, limit=1) -> dict or List[dict]:
|
||||
"""Retrieves an entry from the database by matching a key/value pair. If no
|
||||
key is provided the class objects primary key will be used to match against.
|
||||
|
||||
|
@ -109,12 +92,7 @@ class BaseDocument:
|
|||
if match_key == None:
|
||||
match_key = self.primary_key
|
||||
|
||||
result = (
|
||||
session.query(self.sql_model)
|
||||
.filter_by(**{match_key: match_value})
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
result = session.query(self.sql_model).filter_by(**{match_key: match_value}).limit(limit).all()
|
||||
|
||||
if limit == 1:
|
||||
try:
|
||||
|
@ -167,11 +145,7 @@ class BaseDocument:
|
|||
return return_data
|
||||
|
||||
def delete(self, session: Session, primary_key_value) -> dict:
|
||||
result = (
|
||||
session.query(self.sql_model)
|
||||
.filter_by(**{self.primary_key: primary_key_value})
|
||||
.one()
|
||||
)
|
||||
result = session.query(self.sql_model).filter_by(**{self.primary_key: primary_key_value}).one()
|
||||
|
||||
session.delete(result)
|
||||
session.commit()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from core.config import SQLITE_FILE, USE_SQL
|
||||
from mealie.core.config import SQLITE_FILE, USE_SQL
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from db.models.db_session import sql_global_init
|
||||
from mealie.db.models.db_session import sql_global_init
|
||||
|
||||
sql_exists = True
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from core.config import DEFAULT_GROUP
|
||||
from core.security import get_password_hash
|
||||
from mealie.core.config import DEFAULT_GROUP
|
||||
from mealie.core.security import get_password_hash
|
||||
from fastapi.logger import logger
|
||||
from schema.settings import SiteSettings
|
||||
from schema.theme import SiteTheme
|
||||
from mealie.schema.settings import SiteSettings
|
||||
from mealie.schema.theme import SiteTheme
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from db.database import db
|
||||
from db.db_setup import create_session
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session
|
||||
|
||||
|
||||
def init_db(db: Session = None) -> None:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from db.models.mealplan import *
|
||||
from db.models.recipe.recipe import *
|
||||
from db.models.settings import *
|
||||
from db.models.theme import *
|
||||
from db.models.users import *
|
||||
from db.models.sign_up import *
|
||||
from db.models.group import *
|
||||
from mealie.db.models.mealplan import *
|
||||
from mealie.db.models.recipe.recipe import *
|
||||
from mealie.db.models.settings import *
|
||||
from mealie.db.models.theme import *
|
||||
from mealie.db.models.users import *
|
||||
from mealie.db.models.sign_up import *
|
||||
from mealie.db.models.group import *
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from pathlib import Path
|
||||
|
||||
import sqlalchemy as sa
|
||||
from db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
|
||||
|
@ -18,7 +18,7 @@ def sql_global_init(db_file: Path, check_thread=False):
|
|||
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
import db.models._all_models
|
||||
import mealie.db.models._all_models
|
||||
|
||||
SqlAlchemyBase.metadata.create_all(engine)
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from core.config import DEFAULT_GROUP
|
||||
from db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from db.models.recipe.category import Category, group2categories
|
||||
from mealie.core.config import DEFAULT_GROUP
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.recipe.category import Category, group2categories
|
||||
from fastapi.logger import logger
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
@ -20,18 +20,17 @@ class Group(SqlAlchemyBase, BaseMixins):
|
|||
name = sa.Column(sa.String, index=True, nullable=False, unique=True)
|
||||
users = orm.relationship("User", back_populates="group")
|
||||
mealplans = orm.relationship(
|
||||
"MealPlanModel", back_populates="group", single_parent=True
|
||||
)
|
||||
categories = orm.relationship(
|
||||
"Category", secondary=group2categories, single_parent=True
|
||||
"MealPlanModel",
|
||||
back_populates="group",
|
||||
single_parent=True,
|
||||
order_by="MealPlanModel.startDate",
|
||||
)
|
||||
categories = orm.relationship("Category", secondary=group2categories, single_parent=True)
|
||||
|
||||
# Webhook Settings
|
||||
webhook_enable = sa.Column(sa.Boolean, default=False)
|
||||
webhook_time = sa.Column(sa.String, default="00:00")
|
||||
webhook_urls = orm.relationship(
|
||||
"WebhookURLModel", uselist=True, cascade="all, delete"
|
||||
)
|
||||
webhook_urls = orm.relationship("WebhookURLModel", uselist=True, cascade="all, delete")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -46,10 +45,7 @@ class Group(SqlAlchemyBase, BaseMixins):
|
|||
webhook_urls=[],
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.categories = [
|
||||
Category.get_ref(session=session, slug=cat.get("slug"))
|
||||
for cat in categories
|
||||
]
|
||||
self.categories = [Category.get_ref(session=session, slug=cat.get("slug")) for cat in categories]
|
||||
|
||||
self.webhook_enable = webhook_enable
|
||||
self.webhook_time = webhook_time
|
||||
|
|
|
@ -2,8 +2,8 @@ from typing import List
|
|||
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.models.group import Group
|
||||
from db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
|
||||
|
||||
class Meal(SqlAlchemyBase):
|
||||
|
@ -33,9 +33,7 @@ class MealPlanModel(SqlAlchemyBase, BaseMixins):
|
|||
group_id = sa.Column(sa.String, sa.ForeignKey("groups.id"))
|
||||
group = orm.relationship("Group", back_populates="mealplans")
|
||||
|
||||
def __init__(
|
||||
self, startDate, endDate, meals, group: str, uid=None, session=None
|
||||
) -> None:
|
||||
def __init__(self, startDate, endDate, meals, group: str, uid=None, session=None) -> None:
|
||||
self.startDate = startDate
|
||||
self.endDate = endDate
|
||||
self.group = Group.get_ref(session, group)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from datetime import date
|
||||
|
||||
import sqlalchemy as sa
|
||||
from db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class ApiExtras(SqlAlchemyBase):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from fastapi.logger import logger
|
||||
from slugify import slugify
|
||||
from sqlalchemy.orm import validates
|
||||
|
@ -32,9 +32,7 @@ class Category(SqlAlchemyBase):
|
|||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
name = sa.Column(sa.String, index=True, nullable=False)
|
||||
slug = sa.Column(sa.String, index=True, unique=True, nullable=False)
|
||||
recipes = orm.relationship(
|
||||
"RecipeModel", secondary=recipes2categories, back_populates="recipeCategory"
|
||||
)
|
||||
recipes = orm.relationship("RecipeModel", secondary=recipes2categories, back_populates="recipeCategory")
|
||||
|
||||
@validates("name")
|
||||
def validate_name(self, key, name):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import sqlalchemy as sa
|
||||
from db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class RecipeIngredient(SqlAlchemyBase):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import sqlalchemy as sa
|
||||
from db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class RecipeInstruction(SqlAlchemyBase):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import sqlalchemy as sa
|
||||
from db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class Note(SqlAlchemyBase):
|
||||
|
@ -12,4 +12,3 @@ class Note(SqlAlchemyBase):
|
|||
def __init__(self, title, text) -> None:
|
||||
self.title = title
|
||||
self.text = text
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import sqlalchemy as sa
|
||||
from db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class Nutrition(SqlAlchemyBase):
|
||||
|
@ -28,4 +28,3 @@ class Nutrition(SqlAlchemyBase):
|
|||
self.proteinContent = proteinContent
|
||||
self.sodiumContent = sodiumContent
|
||||
self.sugarContent = sugarContent
|
||||
|
||||
|
|
|
@ -4,15 +4,15 @@ from typing import List
|
|||
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from db.models.recipe.api_extras import ApiExtras
|
||||
from db.models.recipe.category import Category, recipes2categories
|
||||
from db.models.recipe.ingredient import RecipeIngredient
|
||||
from db.models.recipe.instruction import RecipeInstruction
|
||||
from db.models.recipe.note import Note
|
||||
from db.models.recipe.nutrition import Nutrition
|
||||
from db.models.recipe.tag import Tag, recipes2tags
|
||||
from db.models.recipe.tool import Tool
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.recipe.api_extras import ApiExtras
|
||||
from mealie.db.models.recipe.category import Category, recipes2categories
|
||||
from mealie.db.models.recipe.ingredient import RecipeIngredient
|
||||
from mealie.db.models.recipe.instruction import RecipeInstruction
|
||||
from mealie.db.models.recipe.note import Note
|
||||
from mealie.db.models.recipe.nutrition import Nutrition
|
||||
from mealie.db.models.recipe.tag import Tag, recipes2tags
|
||||
from mealie.db.models.recipe.tool import Tool
|
||||
from sqlalchemy.ext.orderinglist import ordering_list
|
||||
from sqlalchemy.orm import validates
|
||||
|
||||
|
@ -33,12 +33,8 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||
recipeYield = sa.Column(sa.String)
|
||||
recipeCuisine = sa.Column(sa.String)
|
||||
tool: List[Tool] = orm.relationship("Tool", cascade="all, delete")
|
||||
nutrition: Nutrition = orm.relationship(
|
||||
"Nutrition", uselist=False, cascade="all, delete"
|
||||
)
|
||||
recipeCategory: List = orm.relationship(
|
||||
"Category", secondary=recipes2categories, back_populates="recipes"
|
||||
)
|
||||
nutrition: Nutrition = orm.relationship("Nutrition", uselist=False, cascade="all, delete")
|
||||
recipeCategory: List = orm.relationship("Category", secondary=recipes2categories, back_populates="recipes")
|
||||
|
||||
recipeIngredient: List[RecipeIngredient] = orm.relationship(
|
||||
"RecipeIngredient",
|
||||
|
@ -55,9 +51,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||
|
||||
# Mealie Specific
|
||||
slug = sa.Column(sa.String, index=True, unique=True)
|
||||
tags: List[Tag] = orm.relationship(
|
||||
"Tag", secondary=recipes2tags, back_populates="recipes"
|
||||
)
|
||||
tags: List[Tag] = orm.relationship("Tag", secondary=recipes2tags, back_populates="recipes")
|
||||
dateAdded = sa.Column(sa.Date, default=date.today)
|
||||
notes: List[Note] = orm.relationship("Note", cascade="all, delete")
|
||||
rating = sa.Column(sa.Integer)
|
||||
|
@ -106,9 +100,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||
self.tool = [Tool(tool=x) for x in tool] if tool else []
|
||||
|
||||
self.recipeYield = recipeYield
|
||||
self.recipeIngredient = [
|
||||
RecipeIngredient(ingredient=ingr) for ingr in recipeIngredient
|
||||
]
|
||||
self.recipeIngredient = [RecipeIngredient(ingredient=ingr) for ingr in recipeIngredient]
|
||||
self.recipeInstructions = [
|
||||
RecipeInstruction(text=instruc.get("text"), type=instruc.get("@type", None))
|
||||
for instruc in recipeInstructions
|
||||
|
@ -117,10 +109,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||
self.prepTime = prepTime
|
||||
self.performTime = performTime
|
||||
|
||||
self.recipeCategory = [
|
||||
Category.create_if_not_exist(session=session, name=cat)
|
||||
for cat in recipeCategory
|
||||
]
|
||||
self.recipeCategory = [Category.create_if_not_exist(session=session, name=cat) for cat in recipeCategory]
|
||||
|
||||
# Mealie Specific
|
||||
self.tags = [Tag.create_if_not_exist(session=session, name=tag) for tag in tags]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from fastapi.logger import logger
|
||||
from slugify import slugify
|
||||
from sqlalchemy.orm import validates
|
||||
|
@ -18,9 +18,7 @@ class Tag(SqlAlchemyBase):
|
|||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
name = sa.Column(sa.String, index=True, nullable=False)
|
||||
slug = sa.Column(sa.String, index=True, unique=True, nullable=False)
|
||||
recipes = orm.relationship(
|
||||
"RecipeModel", secondary=recipes2tags, back_populates="tags"
|
||||
)
|
||||
recipes = orm.relationship("RecipeModel", secondary=recipes2tags, back_populates="tags")
|
||||
|
||||
@validates("name")
|
||||
def validate_name(self, key, name):
|
||||
|
@ -31,7 +29,6 @@ class Tag(SqlAlchemyBase):
|
|||
self.name = name.strip()
|
||||
self.slug = slugify(self.name)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def create_if_not_exist(session, name: str = None):
|
||||
test_slug = slugify(name)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import sqlalchemy as sa
|
||||
from db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class Tool(SqlAlchemyBase):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from db.models.recipe.category import Category, site_settings2categories
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.recipe.category import Category, site_settings2categories
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
|
@ -29,10 +29,7 @@ class SiteSettings(SqlAlchemyBase, BaseMixins):
|
|||
self.language = language
|
||||
self.cards_per_section = cards_per_section
|
||||
self.show_recent = show_recent
|
||||
self.categories = [
|
||||
Category.get_ref(session=session, name=cat.get("slug"))
|
||||
for cat in categories
|
||||
]
|
||||
self.categories = [Category.get_ref(session=session, name=cat.get("slug")) for cat in categories]
|
||||
|
||||
def update(self, *args, **kwarg):
|
||||
self.__init__(*args, **kwarg)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from sqlalchemy import Boolean, Column, Integer, String
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class SiteThemeModel(SqlAlchemyBase):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from core.config import DEFAULT_GROUP
|
||||
from db.models.group import Group
|
||||
from db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.core.config import DEFAULT_GROUP
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, orm
|
||||
|
||||
# I'm not sure this is necessasry, browser based settings may be sufficient
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import operator
|
||||
import shutil
|
||||
|
||||
from core.config import BACKUP_DIR, TEMPLATE_DIR
|
||||
from db.db_setup import generate_session
|
||||
from mealie.core.config import BACKUP_DIR, TEMPLATE_DIR
|
||||
from mealie.db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||
from schema.backup import BackupJob, ImportJob, Imports, LocalBackup
|
||||
from schema.snackbar import SnackResponse
|
||||
from services.backups.exports import backup_all
|
||||
from services.backups.imports import ImportDatabase
|
||||
from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from mealie.services.backups.exports import backup_all
|
||||
from mealie.services.backups.imports import ImportDatabase
|
||||
from sqlalchemy.orm.session import Session
|
||||
from starlette.responses import FileResponse
|
||||
|
||||
|
@ -71,17 +71,13 @@ async def upload_nextcloud_zipfile(file_name: str):
|
|||
file = BACKUP_DIR.joinpath(file_name)
|
||||
|
||||
if file.is_file:
|
||||
return FileResponse(
|
||||
file, media_type="application/octet-stream", filename=file_name
|
||||
)
|
||||
return FileResponse(file, media_type="application/octet-stream", filename=file_name)
|
||||
else:
|
||||
return SnackResponse.error("No File Found")
|
||||
|
||||
|
||||
@router.post("/{file_name}/import", status_code=200)
|
||||
def import_database(
|
||||
file_name: str, import_data: ImportJob, session: Session = Depends(generate_session)
|
||||
):
|
||||
def import_database(file_name: str, import_data: ImportJob, session: Session = Depends(generate_session)):
|
||||
""" Import a database backup file generated from Mealie. """
|
||||
|
||||
import_db = ImportDatabase(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import json
|
||||
|
||||
from core.config import APP_VERSION, DEBUG_DIR, LOGGER_FILE
|
||||
from mealie.core.config import APP_VERSION, DEBUG_DIR, LOGGER_FILE
|
||||
from fastapi import APIRouter
|
||||
|
||||
router = APIRouter(prefix="/api/debug", tags=["Debug"])
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
from core.config import SECRET
|
||||
from db.database import db
|
||||
from db.db_setup import create_session
|
||||
from mealie.core.config import SECRET
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session
|
||||
from fastapi_login import LoginManager
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from schema.user import UserInDB
|
||||
from mealie.schema.user import UserInDB
|
||||
|
||||
manager = LoginManager(SECRET, "/api/auth/token")
|
||||
|
||||
|
@ -21,4 +21,3 @@ def query_user(user_email: str, session: Session = None) -> UserInDB:
|
|||
user = db.users.get(session, user_email, "email")
|
||||
session.close()
|
||||
return user
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from routes.deps import manager
|
||||
from schema.snackbar import SnackResponse
|
||||
from schema.user import GroupBase, GroupInDB, UpdateGroup, UserInDB
|
||||
from mealie.routes.deps import manager
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from mealie.schema.user import GroupBase, GroupInDB, UpdateGroup, UserInDB
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
router = APIRouter(prefix="/api/groups", tags=["Groups"])
|
||||
|
@ -59,9 +59,7 @@ async def update_group_data(
|
|||
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_user_group(
|
||||
id: int, current_user=Depends(manager), session: Session = Depends(generate_session)
|
||||
):
|
||||
async def delete_user_group(id: int, current_user=Depends(manager), session: Session = Depends(generate_session)):
|
||||
""" Removes a user group from the database """
|
||||
|
||||
if id == 1:
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from fastapi import APIRouter
|
||||
from routes.groups import crud
|
||||
from mealie.routes.groups import crud
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(crud.router)
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import datetime
|
||||
|
||||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from routes.deps import manager
|
||||
from schema.meal import MealPlanIn, MealPlanInDB
|
||||
from schema.snackbar import SnackResponse
|
||||
from schema.user import GroupInDB, UserInDB
|
||||
from services.meal_services import get_todays_meal, process_meals
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import manager
|
||||
from mealie.schema.meal import MealPlanIn, MealPlanInDB
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from mealie.schema.user import GroupInDB, UserInDB
|
||||
from mealie.services.meal_services import get_todays_meal, process_meals
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
router = APIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
|
||||
|
@ -37,9 +37,7 @@ def create_meal_plan(
|
|||
|
||||
|
||||
@router.put("/{plan_id}")
|
||||
def update_meal_plan(
|
||||
plan_id: str, meal_plan: MealPlanIn, session: Session = Depends(generate_session)
|
||||
):
|
||||
def update_meal_plan(plan_id: str, meal_plan: MealPlanIn, session: Session = Depends(generate_session)):
|
||||
""" Updates a meal plan based off ID """
|
||||
processed_plan = process_meals(session, meal_plan)
|
||||
processed_plan = MealPlanInDB(uid=plan_id, **processed_plan.dict())
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from schema.meal import MealPlanInDB
|
||||
from schema.recipe import Recipe
|
||||
from mealie.schema.meal import MealPlanInDB
|
||||
from mealie.schema.recipe import Recipe
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
router = APIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
|
||||
|
@ -16,8 +16,6 @@ def get_shopping_list(id: str, session: Session = Depends(generate_session)):
|
|||
mealplan: MealPlanInDB
|
||||
slugs = [x.slug for x in mealplan.meals]
|
||||
recipes: list[Recipe] = [db.recipes.get(session, x) for x in slugs]
|
||||
ingredients = [
|
||||
{"name": x.name, "recipeIngredient": x.recipeIngredient} for x in recipes if x
|
||||
]
|
||||
ingredients = [{"name": x.name, "recipeIngredient": x.recipeIngredient} for x in recipes if x]
|
||||
|
||||
return ingredients
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from fastapi import APIRouter
|
||||
from routes.mealplans import crud, helpers
|
||||
from mealie.routes.mealplans import crud, helpers
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
|
@ -2,14 +2,14 @@ import operator
|
|||
import shutil
|
||||
from typing import List
|
||||
|
||||
from core.config import MIGRATION_DIR
|
||||
from db.db_setup import generate_session
|
||||
from mealie.core.config import MIGRATION_DIR
|
||||
from mealie.db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||
from schema.migration import MigrationFile, Migrations
|
||||
from services.migrations.chowdown import chowdown_migrate as chowdow_migrate
|
||||
from services.migrations.nextcloud import migrate as nextcloud_migrate
|
||||
from mealie.schema.migration import MigrationFile, Migrations
|
||||
from mealie.services.migrations.chowdown import chowdown_migrate as chowdow_migrate
|
||||
from mealie.services.migrations.nextcloud import migrate as nextcloud_migrate
|
||||
from sqlalchemy.orm.session import Session
|
||||
from schema.snackbar import SnackResponse
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
|
||||
router = APIRouter(prefix="/api/migrations", tags=["Migration"])
|
||||
|
||||
|
@ -36,9 +36,7 @@ def get_avaiable_nextcloud_imports():
|
|||
|
||||
|
||||
@router.post("/{type}/{file_name}/import")
|
||||
def import_nextcloud_directory(
|
||||
type: str, file_name: str, session: Session = Depends(generate_session)
|
||||
):
|
||||
def import_nextcloud_directory(type: str, file_name: str, session: Session = Depends(generate_session)):
|
||||
""" Imports all the recipes in a given directory """
|
||||
file_path = MIGRATION_DIR.joinpath(type, file_name)
|
||||
if type == "nextcloud":
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from schema.recipe import AllRecipeRequest
|
||||
from mealie.schema.recipe import AllRecipeRequest
|
||||
from slugify import slugify
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
@ -44,9 +44,7 @@ def get_all_recipes(
|
|||
|
||||
|
||||
@router.post("/api/recipes")
|
||||
def get_all_recipes_post(
|
||||
body: AllRecipeRequest, session: Session = Depends(generate_session)
|
||||
):
|
||||
def get_all_recipes_post(body: AllRecipeRequest, session: Session = Depends(generate_session)):
|
||||
"""
|
||||
Returns key data for all recipes based off the body data provided.
|
||||
For example, if slug, image, and name are provided you will recieve a list of
|
||||
|
@ -76,9 +74,7 @@ def get_all_recipes_post(
|
|||
def filter_by_category(categories: list, session: Session = Depends(generate_session)):
|
||||
""" pass a list of categories and get a list of recipes associated with those categories """
|
||||
#! This should be refactored into a single database call, but I couldn't figure it out
|
||||
in_category = [
|
||||
db.categories.get(session, slugify(cat), limit=1) for cat in categories
|
||||
]
|
||||
in_category = [db.categories.get(session, slugify(cat), limit=1) for cat in categories]
|
||||
in_category = [cat.get("recipes") for cat in in_category if cat]
|
||||
in_category = [item for sublist in in_category for item in sublist]
|
||||
return in_category
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from schema.category import RecipeCategoryResponse
|
||||
from mealie.schema.category import RecipeCategoryResponse
|
||||
from sqlalchemy.orm.session import Session
|
||||
from schema.snackbar import SnackResponse
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
|
||||
from schema.snackbar import SnackResponse
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/categories",
|
||||
|
@ -20,17 +20,13 @@ async def get_all_recipe_categories(session: Session = Depends(generate_session)
|
|||
|
||||
|
||||
@router.get("/{category}", response_model=RecipeCategoryResponse)
|
||||
def get_all_recipes_by_category(
|
||||
category: str, session: Session = Depends(generate_session)
|
||||
):
|
||||
def get_all_recipes_by_category(category: str, session: Session = Depends(generate_session)):
|
||||
""" Returns a list of recipes associated with the provided category. """
|
||||
return db.categories.get(session, category)
|
||||
|
||||
|
||||
@router.delete("/{category}")
|
||||
async def delete_recipe_category(
|
||||
category: str, session: Session = Depends(generate_session)
|
||||
):
|
||||
async def delete_recipe_category(category: str, session: Session = Depends(generate_session)):
|
||||
"""Removes a recipe category from the database. Deleting a
|
||||
category does not impact a recipe. The category will be removed
|
||||
from any recipes that contain it"""
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, File, Form, HTTPException
|
||||
from fastapi.logger import logger
|
||||
from fastapi.responses import FileResponse
|
||||
from schema.recipe import Recipe, RecipeURLIn
|
||||
from schema.snackbar import SnackResponse
|
||||
from services.image_services import read_image, write_image
|
||||
from services.scraper.scraper import create_from_url
|
||||
from mealie.schema.recipe import Recipe, RecipeURLIn
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from mealie.services.image_services import read_image, write_image
|
||||
from mealie.services.scraper.scraper import create_from_url
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
router = APIRouter(
|
||||
|
@ -41,9 +41,7 @@ def get_recipe(recipe_slug: str, session: Session = Depends(generate_session)):
|
|||
|
||||
|
||||
@router.put("/{recipe_slug}")
|
||||
def update_recipe(
|
||||
recipe_slug: str, data: Recipe, session: Session = Depends(generate_session)
|
||||
):
|
||||
def update_recipe(recipe_slug: str, data: Recipe, session: Session = Depends(generate_session)):
|
||||
""" Updates a recipe by existing slug and data. """
|
||||
|
||||
recipe: Recipe = db.recipes.update(session, recipe_slug, data.dict())
|
||||
|
@ -58,9 +56,7 @@ def delete_recipe(recipe_slug: str, session: Session = Depends(generate_session)
|
|||
try:
|
||||
db.recipes.delete(session, recipe_slug)
|
||||
except:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=SnackResponse.error("Unable to Delete Recipe")
|
||||
)
|
||||
raise HTTPException(status_code=404, detail=SnackResponse.error("Unable to Delete Recipe"))
|
||||
|
||||
return SnackResponse.error(f"Recipe {recipe_slug} Deleted")
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm.session import Session
|
||||
from schema.snackbar import SnackResponse
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
|
||||
from schema.snackbar import SnackResponse
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
|
||||
router = APIRouter(tags=["Recipes"])
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from schema.settings import SiteSettings
|
||||
from schema.snackbar import SnackResponse
|
||||
from schema.user import GroupInDB, UserInDB
|
||||
from mealie.schema.settings import SiteSettings
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from mealie.schema.user import GroupInDB, UserInDB
|
||||
from sqlalchemy.orm.session import Session
|
||||
from utils.post_webhooks import post_webhooks
|
||||
from mealie.utils.post_webhooks import post_webhooks
|
||||
|
||||
from routes.deps import manager
|
||||
from mealie.routes.deps import manager
|
||||
|
||||
router = APIRouter(prefix="/api/site-settings", tags=["Settings"])
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from db.db_setup import generate_session
|
||||
from mealie.db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from schema.theme import SiteTheme
|
||||
from mealie.schema.theme import SiteTheme
|
||||
from sqlalchemy.orm.session import Session
|
||||
from schema.snackbar import SnackResponse
|
||||
from db.database import db
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from mealie.db.database import db
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["Themes"])
|
||||
|
||||
|
@ -30,9 +30,7 @@ def get_single_theme(theme_name: str, session: Session = Depends(generate_sessio
|
|||
|
||||
|
||||
@router.put("/themes/{theme_name}")
|
||||
def update_theme(
|
||||
theme_name: str, data: SiteTheme, session: Session = Depends(generate_session)
|
||||
):
|
||||
def update_theme(theme_name: str, data: SiteTheme, session: Session = Depends(generate_session)):
|
||||
""" Update a theme database entry """
|
||||
db.themes.update(session, theme_name, data.dict())
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from core.security import verify_password
|
||||
from db.db_setup import generate_session
|
||||
from mealie.core.security import verify_password
|
||||
from mealie.db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from fastapi_login.exceptions import InvalidCredentialsException
|
||||
from routes.deps import manager, query_user
|
||||
from schema.snackbar import SnackResponse
|
||||
from schema.user import UserInDB
|
||||
from mealie.routes.deps import manager, query_user
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from mealie.schema.user import UserInDB
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
router = APIRouter(prefix="/api/auth", tags=["Authentication"])
|
||||
|
@ -27,9 +27,7 @@ def get_token(
|
|||
elif not verify_password(password, user.password):
|
||||
raise InvalidCredentialsException
|
||||
|
||||
access_token = manager.create_access_token(
|
||||
data=dict(sub=email), expires=timedelta(hours=2)
|
||||
)
|
||||
access_token = manager.create_access_token(data=dict(sub=email), expires=timedelta(hours=2))
|
||||
return SnackResponse.success(
|
||||
"User Successfully Logged In",
|
||||
{"access_token": access_token, "token_type": "bearer"},
|
||||
|
@ -51,9 +49,7 @@ def get_long_token(
|
|||
elif not verify_password(password, user.password):
|
||||
raise InvalidCredentialsException
|
||||
|
||||
access_token = manager.create_access_token(
|
||||
data=dict(sub=email), expires=timedelta(days=1)
|
||||
)
|
||||
access_token = manager.create_access_token(data=dict(sub=email), expires=timedelta(days=1))
|
||||
return SnackResponse.success(
|
||||
"User Successfully Logged In",
|
||||
{"access_token": access_token, "token_type": "bearer"},
|
||||
|
@ -63,7 +59,5 @@ def get_long_token(
|
|||
@router.get("/refresh")
|
||||
async def refresh_token(current_user: UserInDB = Depends(manager)):
|
||||
""" Use a valid token to get another token"""
|
||||
access_token = manager.create_access_token(
|
||||
data=dict(sub=current_user.email), expires=timedelta(hours=1)
|
||||
)
|
||||
access_token = manager.create_access_token(data=dict(sub=current_user.email), expires=timedelta(hours=1))
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
|
|
@ -2,15 +2,15 @@ import shutil
|
|||
from datetime import timedelta
|
||||
from os import access
|
||||
|
||||
from core.config import USER_DIR
|
||||
from core.security import get_password_hash, verify_password
|
||||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from mealie.core.config import USER_DIR
|
||||
from mealie.core.security import get_password_hash, verify_password
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, File, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
from routes.deps import manager
|
||||
from schema.snackbar import SnackResponse
|
||||
from schema.user import ChangePassword, UserBase, UserIn, UserInDB, UserOut
|
||||
from mealie.routes.deps import manager
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from mealie.schema.user import ChangePassword, UserBase, UserIn, UserInDB, UserOut
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
router = APIRouter(prefix="/api/users", tags=["Users"])
|
||||
|
@ -71,9 +71,7 @@ async def update_user(
|
|||
updated_user: UserInDB = db.users.update(session, id, new_data.dict())
|
||||
email = updated_user.email
|
||||
if current_user.id == id:
|
||||
access_token = manager.create_access_token(
|
||||
data=dict(sub=email), expires=timedelta(hours=2)
|
||||
)
|
||||
access_token = manager.create_access_token(data=dict(sub=email), expires=timedelta(hours=2))
|
||||
access_token = {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
return SnackResponse.success("User Updated", access_token)
|
||||
|
@ -126,9 +124,7 @@ async def update_password(
|
|||
):
|
||||
""" Resets the User Password"""
|
||||
|
||||
match_passwords = verify_password(
|
||||
password_change.current_password, current_user.password
|
||||
)
|
||||
match_passwords = verify_password(password_change.current_password, current_user.password)
|
||||
match_id = current_user.id == id
|
||||
|
||||
if match_passwords and match_id:
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import uuid
|
||||
|
||||
from core.security import get_password_hash
|
||||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from mealie.core.security import get_password_hash
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from routes.deps import manager
|
||||
from schema.sign_up import SignUpIn, SignUpOut, SignUpToken
|
||||
from schema.snackbar import SnackResponse
|
||||
from schema.user import UserIn, UserInDB
|
||||
from mealie.routes.deps import manager
|
||||
from mealie.schema.sign_up import SignUpIn, SignUpOut, SignUpToken
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from mealie.schema.user import UserIn, UserInDB
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
router = APIRouter(prefix="/api/users/sign-ups", tags=["User Signup"])
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from fastapi import APIRouter
|
||||
from routes.users import auth, crud, sign_up
|
||||
from mealie.routes.users import auth, crud, sign_up
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
|
@ -9,4 +9,4 @@
|
|||
caddy start --config ./Caddyfile
|
||||
|
||||
## Start API
|
||||
uvicorn app:app --host 0.0.0.0 --port 9000
|
||||
uvicorn mealie.app:app --host 0.0.0.0 --port 9000
|
|
@ -8,6 +8,8 @@ class BackupOptions(BaseModel):
|
|||
recipes: bool = True
|
||||
settings: bool = True
|
||||
themes: bool = True
|
||||
groups: bool = True
|
||||
users: bool = True
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
|
@ -15,6 +17,8 @@ class BackupOptions(BaseModel):
|
|||
"recipes": True,
|
||||
"settings": True,
|
||||
"themes": True,
|
||||
"groups": True,
|
||||
"users": True,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ from typing import List, Optional
|
|||
|
||||
from fastapi_camelcase import CamelModel
|
||||
|
||||
from schema.recipe import Recipe
|
||||
from mealie.schema.recipe import Recipe
|
||||
|
||||
|
||||
class CategoryBase(CamelModel):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from datetime import date
|
||||
from typing import List, Optional
|
||||
|
||||
from db.models.mealplan import MealPlanModel
|
||||
from mealie.db.models.mealplan import MealPlanModel
|
||||
from pydantic import BaseModel, validator
|
||||
from pydantic.utils import GetterDict
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import datetime
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from db.models.recipe.recipe import RecipeModel
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
from pydantic import BaseModel, validator
|
||||
from pydantic.utils import GetterDict
|
||||
from slugify import slugify
|
||||
|
|
|
@ -9,12 +9,14 @@ class RecipeImport(BaseModel):
|
|||
status: bool
|
||||
exception: Optional[str]
|
||||
|
||||
|
||||
class ThemeImport(BaseModel):
|
||||
name: str
|
||||
status: bool
|
||||
exception: Optional[str]
|
||||
|
||||
|
||||
class SettingsImport(BaseModel):
|
||||
name: str
|
||||
status: bool
|
||||
exception: Optional[str]
|
||||
exception: Optional[str]
|
||||
|
|
|
@ -2,7 +2,7 @@ from typing import Optional
|
|||
|
||||
from fastapi_camelcase import CamelModel
|
||||
|
||||
from schema.category import CategoryBase
|
||||
from mealie.schema.category import CategoryBase
|
||||
|
||||
|
||||
class SiteSettings(CamelModel):
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from typing import Any, Optional
|
||||
|
||||
from core.config import DEFAULT_GROUP
|
||||
from db.models.group import Group
|
||||
from db.models.users import User
|
||||
from mealie.core.config import DEFAULT_GROUP
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.users import User
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic.utils import GetterDict
|
||||
|
||||
from schema.category import CategoryBase
|
||||
from schema.meal import MealPlanInDB
|
||||
from mealie.schema.category import CategoryBase
|
||||
from mealie.schema.meal import MealPlanInDB
|
||||
|
||||
|
||||
class ChangePassword(CamelModel):
|
||||
|
|
0
mealie/services/__init__.py
Normal file
0
mealie/services/__init__.py
Normal file
0
mealie/services/backups/__init__.py
Normal file
0
mealie/services/backups/__init__.py
Normal file
|
@ -4,13 +4,12 @@ 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 mealie.core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR, TEMPLATE_DIR
|
||||
from mealie.db.database import db
|
||||
from mealie.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:
|
||||
|
@ -75,14 +74,10 @@ class ExportDatabase:
|
|||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if export_list:
|
||||
ExportDatabase._write_json_file(
|
||||
items, out_dir.joinpath(f"{folder_name}.json")
|
||||
)
|
||||
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")
|
||||
)
|
||||
ExportDatabase._write_json_file(item, out_dir.joinpath(f"{item.get('name')}.json"))
|
||||
|
||||
@staticmethod
|
||||
def _write_json_file(data: Union[dict, list], out_file: Path):
|
||||
|
|
|
@ -4,13 +4,13 @@ import zipfile
|
|||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR
|
||||
from db.database import db
|
||||
from db.db_setup import create_session
|
||||
from mealie.core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session
|
||||
from fastapi.logger import logger
|
||||
from schema.recipe import Recipe
|
||||
from schema.restore import RecipeImport, SettingsImport, ThemeImport
|
||||
from schema.theme import SiteTheme
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.schema.restore import RecipeImport, SettingsImport, ThemeImport
|
||||
from mealie.schema.theme import SiteTheme
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
||||
|
@ -93,9 +93,7 @@ class ImportDatabase:
|
|||
|
||||
recipe_obj = Recipe(**recipe_dict)
|
||||
db.recipes.create(session, recipe_obj.dict())
|
||||
import_status = RecipeImport(
|
||||
name=recipe_obj.name, slug=recipe_obj.slug, status=True
|
||||
)
|
||||
import_status = RecipeImport(name=recipe_obj.name, slug=recipe_obj.slug, status=True)
|
||||
imports.append(import_status)
|
||||
successful_imports.append(recipe.stem)
|
||||
logger.info(f"Imported: {recipe.stem}")
|
||||
|
@ -125,17 +123,13 @@ class ImportDatabase:
|
|||
# Migration from list to Object Type Data
|
||||
try:
|
||||
if "" in recipe_dict["tags"]:
|
||||
recipe_dict["tags"] = [
|
||||
tag for tag in recipe_dict["tags"] if not tag == ""
|
||||
]
|
||||
recipe_dict["tags"] = [tag for tag in recipe_dict["tags"] if not tag == ""]
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
if "" in recipe_dict["categories"]:
|
||||
recipe_dict["categories"] = [
|
||||
cat for cat in recipe_dict["categories"] if not cat == ""
|
||||
]
|
||||
recipe_dict["categories"] = [cat for cat in recipe_dict["categories"] if not cat == ""]
|
||||
except:
|
||||
pass
|
||||
|
||||
|
@ -165,9 +159,7 @@ class ImportDatabase:
|
|||
theme_imports.append(ThemeImport(name=new_theme.name, status=True))
|
||||
except Exception as inst:
|
||||
logger.info(f"Unable Import Theme {new_theme.name}")
|
||||
theme_imports.append(
|
||||
ThemeImport(name=new_theme.name, status=False, exception=str(inst))
|
||||
)
|
||||
theme_imports.append(ThemeImport(name=new_theme.name, status=False, exception=str(inst)))
|
||||
|
||||
return theme_imports
|
||||
|
||||
|
@ -185,9 +177,7 @@ class ImportDatabase:
|
|||
import_status = SettingsImport(name=name, status=True)
|
||||
|
||||
except Exception as inst:
|
||||
import_status = SettingsImport(
|
||||
name=name, status=False, exception=str(inst)
|
||||
)
|
||||
import_status = SettingsImport(name=name, status=False, exception=str(inst))
|
||||
|
||||
settings_imports.append(import_status)
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import shutil
|
|||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from core.config import IMG_DIR
|
||||
from mealie.core.config import IMG_DIR
|
||||
from fastapi.logger import logger
|
||||
|
||||
|
||||
|
|
|
@ -2,12 +2,18 @@ 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 mealie.db.database import db
|
||||
from mealie.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 mealie.schema.meal import (
|
||||
MealIn,
|
||||
MealOut,
|
||||
MealPlanIn,
|
||||
MealPlanInDB,
|
||||
MealPlanProcessed,
|
||||
)
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.schema.user import GroupInDB
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
||||
|
|
0
mealie/services/migrations/__init__.py
Normal file
0
mealie/services/migrations/__init__.py
Normal file
|
@ -2,11 +2,11 @@ import shutil
|
|||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from core.config import IMG_DIR, TEMP_DIR
|
||||
from db.database import db
|
||||
from schema.recipe import Recipe
|
||||
from mealie.core.config import IMG_DIR, TEMP_DIR
|
||||
from mealie.db.database import db
|
||||
from mealie.schema.recipe import Recipe
|
||||
from sqlalchemy.orm.session import Session
|
||||
from utils.unzip import unpack_zip
|
||||
from mealie.utils.unzip import unpack_zip
|
||||
|
||||
try:
|
||||
from yaml import CLoader as Loader
|
||||
|
|
|
@ -4,11 +4,11 @@ import shutil
|
|||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from core.config import IMG_DIR, MIGRATION_DIR, TEMP_DIR
|
||||
from schema.recipe import Recipe
|
||||
from services.scraper.cleaner import Cleaner
|
||||
from core.config import IMG_DIR, TEMP_DIR
|
||||
from db.database import db
|
||||
from mealie.core.config import IMG_DIR, MIGRATION_DIR, TEMP_DIR
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.services.scraper.cleaner import Cleaner
|
||||
from mealie.core.config import IMG_DIR, TEMP_DIR
|
||||
from mealie.db.database import db
|
||||
|
||||
|
||||
def process_selection(selection: Path) -> Path:
|
||||
|
@ -79,7 +79,7 @@ def migrate(session, selection: str):
|
|||
try:
|
||||
recipe = import_recipes(dir)
|
||||
db.recipes.create(session, recipe.dict())
|
||||
|
||||
|
||||
successful_imports.append(recipe.name)
|
||||
except:
|
||||
logging.error(f"Failed Nextcloud Import: {dir.name}")
|
||||
|
|
0
mealie/services/scheduler/__init__.py
Normal file
0
mealie/services/scheduler/__init__.py
Normal file
|
@ -1,3 +1,3 @@
|
|||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
|
||||
scheduler = BackgroundScheduler()
|
||||
scheduler = BackgroundScheduler()
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from db.database import db
|
||||
from db.db_setup import create_session
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session
|
||||
from fastapi.logger import logger
|
||||
from schema.user import GroupInDB
|
||||
from services.backups.exports import auto_backup_job
|
||||
from services.scheduler.global_scheduler import scheduler
|
||||
from services.scheduler.scheduler_utils import Cron, cron_parser
|
||||
from utils.post_webhooks import post_webhooks
|
||||
from mealie.schema.user import GroupInDB
|
||||
from mealie.services.backups.exports import auto_backup_job
|
||||
from mealie.services.scheduler.global_scheduler import scheduler
|
||||
from mealie.services.scheduler.scheduler_utils import Cron, cron_parser
|
||||
from mealie.utils.post_webhooks import post_webhooks
|
||||
|
||||
|
||||
# TODO Fix Scheduler
|
||||
|
@ -83,9 +83,7 @@ def init_webhook_schedule(scheduler, job_store: dict):
|
|||
logger.info("----INIT SCHEDULE OBJECT-----")
|
||||
|
||||
JOB_STORE = {
|
||||
"backup_job": ScheduledFunction(
|
||||
scheduler, auto_backup_job, Cron(hours=00, minutes=00), "backups"
|
||||
),
|
||||
"backup_job": ScheduledFunction(scheduler, auto_backup_job, Cron(hours=00, minutes=00), "backups"),
|
||||
}
|
||||
|
||||
JOB_STORE = init_webhook_schedule(scheduler=scheduler, job_store=JOB_STORE)
|
||||
|
|
0
mealie/services/scraper/__init__.py
Normal file
0
mealie/services/scraper/__init__.py
Normal file
|
@ -31,24 +31,15 @@ class Cleaner:
|
|||
recipe_data["prepTime"] = Cleaner.time(recipe_data.get("prepTime", None))
|
||||
recipe_data["performTime"] = Cleaner.time(recipe_data.get("performTime", None))
|
||||
recipe_data["totalTime"] = Cleaner.time(recipe_data.get("totalTime", None))
|
||||
recipe_data["recipeCategory"] = Cleaner.category(
|
||||
recipe_data.get("recipeCategory", [])
|
||||
)
|
||||
recipe_data["recipeCategory"] = Cleaner.category(recipe_data.get("recipeCategory", []))
|
||||
|
||||
recipe_data["recipeYield"] = Cleaner.yield_amount(
|
||||
recipe_data.get("recipeYield")
|
||||
)
|
||||
recipe_data["recipeIngredient"] = Cleaner.ingredient(
|
||||
recipe_data.get("recipeIngredient")
|
||||
)
|
||||
recipe_data["recipeInstructions"] = Cleaner.instructions(
|
||||
recipe_data["recipeInstructions"]
|
||||
)
|
||||
recipe_data["recipeYield"] = Cleaner.yield_amount(recipe_data.get("recipeYield"))
|
||||
recipe_data["recipeIngredient"] = Cleaner.ingredient(recipe_data.get("recipeIngredient"))
|
||||
recipe_data["recipeInstructions"] = Cleaner.instructions(recipe_data["recipeInstructions"])
|
||||
recipe_data["image"] = Cleaner.image(recipe_data.get("image"))
|
||||
recipe_data["slug"] = slugify(recipe_data.get("name"))
|
||||
recipe_data["orgURL"] = url
|
||||
|
||||
|
||||
return recipe_data
|
||||
|
||||
@staticmethod
|
||||
|
@ -84,11 +75,7 @@ class Cleaner:
|
|||
|
||||
# One long string split by (possibly multiple) new lines
|
||||
if isinstance(instructions, str):
|
||||
return [
|
||||
{"text": Cleaner._instruction(line)}
|
||||
for line in instructions.splitlines()
|
||||
if line
|
||||
]
|
||||
return [{"text": Cleaner._instruction(line)} for line in instructions.splitlines() if line]
|
||||
|
||||
# Plain strings in a list
|
||||
elif type(instructions) == list and type(instructions[0]) == str:
|
||||
|
@ -97,13 +84,8 @@ class Cleaner:
|
|||
# Dictionaries (let's assume it's a HowToStep) in a list
|
||||
elif type(instructions) == list and type(instructions[0]) == dict:
|
||||
# Try List of Dictionary without "@type" or "type"
|
||||
if not instructions[0].get("@type", False) and not instructions[0].get(
|
||||
"type", False
|
||||
):
|
||||
return [
|
||||
{"text": Cleaner._instruction(step["text"])}
|
||||
for step in instructions
|
||||
]
|
||||
if not instructions[0].get("@type", False) and not instructions[0].get("type", False):
|
||||
return [{"text": Cleaner._instruction(step["text"])} for step in instructions]
|
||||
|
||||
try:
|
||||
# If HowToStep is under HowToSection
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from typing import Tuple
|
||||
|
||||
import extruct
|
||||
from core.config import DEBUG_DIR
|
||||
from mealie.core.config import DEBUG_DIR
|
||||
from slugify import slugify
|
||||
from w3lib.html import get_base_url
|
||||
|
||||
|
|
|
@ -3,12 +3,12 @@ from typing import List
|
|||
|
||||
import requests
|
||||
import scrape_schema_recipe
|
||||
from core.config import DEBUG_DIR
|
||||
from mealie.core.config import DEBUG_DIR
|
||||
from fastapi.logger import logger
|
||||
from services.image_services import scrape_image
|
||||
from schema.recipe import Recipe
|
||||
from services.scraper import open_graph
|
||||
from services.scraper.cleaner import Cleaner
|
||||
from mealie.services.image_services import scrape_image
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.services.scraper import open_graph
|
||||
from mealie.services.scraper.cleaner import Cleaner
|
||||
|
||||
LAST_JSON = DEBUG_DIR.joinpath("last_recipe.json")
|
||||
|
||||
|
@ -36,15 +36,11 @@ def create_from_url(url: str) -> Recipe:
|
|||
|
||||
def extract_recipe_from_html(html: str, url: str) -> dict:
|
||||
try:
|
||||
scraped_recipes: List[dict] = scrape_schema_recipe.loads(
|
||||
html, python_objects=True
|
||||
)
|
||||
scraped_recipes: List[dict] = scrape_schema_recipe.loads(html, python_objects=True)
|
||||
dump_last_json(scraped_recipes)
|
||||
|
||||
if not scraped_recipes:
|
||||
scraped_recipes: List[dict] = scrape_schema_recipe.scrape_url(
|
||||
url, python_objects=True
|
||||
)
|
||||
scraped_recipes: List[dict] = scrape_schema_recipe.scrape_url(url, python_objects=True)
|
||||
except Exception as e:
|
||||
# trying without python_objects
|
||||
scraped_recipes: List[dict] = scrape_schema_recipe.loads(html)
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[pytest]
|
||||
python_files = test_*
|
||||
python_classes = *Tests
|
||||
python_functions = test_*
|
0
mealie/utils/__init__.py
Normal file
0
mealie/utils/__init__.py
Normal file
|
@ -1,6 +1,6 @@
|
|||
import json
|
||||
|
||||
from core.config import DATA_DIR
|
||||
from mealie.core.config import DATA_DIR
|
||||
|
||||
"""Script to export the ReDoc documentation page into a standalone HTML file."""
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import requests
|
||||
from db.database import db
|
||||
from db.db_setup import create_session
|
||||
from schema.user import GroupInDB
|
||||
from services.meal_services import get_todays_meal
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session
|
||||
from mealie.schema.user import GroupInDB
|
||||
from mealie.services.meal_services import get_todays_meal
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
||||
|
@ -11,7 +11,7 @@ def post_webhooks(group: int, session: Session = None):
|
|||
group_settings: GroupInDB = db.groups.get(session, group)
|
||||
|
||||
if not group_settings.webhook_enable:
|
||||
return
|
||||
return
|
||||
|
||||
todays_recipe = get_todays_meal(session, group)
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import tempfile
|
|||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from core.config import TEMP_DIR
|
||||
from mealie.core.config import TEMP_DIR
|
||||
|
||||
|
||||
def unpack_zip(selection: Path) -> tempfile.TemporaryDirectory:
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue