mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
Merge pull request #49 from hay-kot/api-docs
Improved API Documentation, New ENV Varibles and Minor refactoring/renaming
This commit is contained in:
commit
a731b9f6ab
20
.github/workflows/build-docs.yml
vendored
Normal file
20
.github/workflows/build-docs.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
name: Publish docs via GitHub Pages
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Deploy docs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout main
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Deploy docs
|
||||||
|
uses: mhausenblas/mkdocs-deploy-gh-pages@master
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CONFIG_FILE: docs/mkdocs.yml
|
||||||
|
EXTRA_PACKAGES: build-base
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,6 +5,9 @@ __pycache__/
|
|||||||
*$py.class
|
*$py.class
|
||||||
# frontend/.env.development
|
# frontend/.env.development
|
||||||
docs/site/
|
docs/site/
|
||||||
|
mealie/temp/*
|
||||||
|
mealie/temp/api.html
|
||||||
|
|
||||||
|
|
||||||
mealie/data/backups/*
|
mealie/data/backups/*
|
||||||
mealie/data/debug/*
|
mealie/data/debug/*
|
||||||
|
@ -20,19 +20,21 @@
|
|||||||
A Place for All Your Recipes
|
A Place for All Your Recipes
|
||||||
<br />
|
<br />
|
||||||
<a href="https://hay-kot.github.io/mealie/"><strong>Explore the docs »</strong></a>
|
<a href="https://hay-kot.github.io/mealie/"><strong>Explore the docs »</strong></a>
|
||||||
<br />
|
<a href="https://github.com/hay-kot/mealie">
|
||||||
|
</a>
|
||||||
<br />
|
<br />
|
||||||
<a href="https://github.com/hay-kot/mealie"><s>View Demo</s></a>
|
<a href="https://github.com/hay-kot/mealie"><s>View Demo</s></a>
|
||||||
·
|
·
|
||||||
<a href="https://github.com/hay-kot/mealie/issues">Report Bug</a>
|
<a href="https://github.com/hay-kot/mealie/issues">Report Bug</a>
|
||||||
·
|
·
|
||||||
|
<a href="/api/docs">API</a>
|
||||||
|
·
|
||||||
<a href="https://github.com/hay-kot/mealie/issues">
|
<a href="https://github.com/hay-kot/mealie/issues">
|
||||||
Request Feature
|
Request Feature
|
||||||
</a>
|
</a>
|
||||||
·
|
·
|
||||||
<a href="https://hub.docker.com/repository/docker/hkotel/mealies"> Docker Hub
|
<a href="https://hub.docker.com/repository/docker/hkotel/mealies"> Docker Hub
|
||||||
</a>
|
</a>
|
||||||
</p>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
5
docs/docs/api/api-examples.md
Normal file
5
docs/docs/api/api-examples.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# API Examples
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Have Ideas? Submit a PR!
|
@ -1,3 +0,0 @@
|
|||||||
# API Introduction
|
|
||||||
|
|
||||||
TODO
|
|
26
docs/docs/api/docs/index.html
Normal file
26
docs/docs/api/docs/index.html
Normal file
File diff suppressed because one or more lines are too long
@ -16,6 +16,7 @@ To deploy docker on your local network it is highly recommended to use docker to
|
|||||||
| db_password | example | The Mongodb password you specified in your mongo container |
|
| db_password | example | The Mongodb password you specified in your mongo container |
|
||||||
| db_host | mongo | The host address of MongoDB if you're in docker and using the same network you can use mongo as the host name |
|
| db_host | mongo | The host address of MongoDB if you're in docker and using the same network you can use mongo as the host name |
|
||||||
| db_port | 27017 | the port to access MongoDB 27017 is the default for mongo |
|
| db_port | 27017 | the port to access MongoDB 27017 is the default for mongo |
|
||||||
|
| api_docs | True | Turns on/off access to the API documentation locally. |
|
||||||
| TZ | | You should set your time zone accordingly so the date/time features work correctly |
|
| TZ | | You should set your time zone accordingly so the date/time features work correctly |
|
||||||
|
|
||||||
|
|
||||||
|
26
docs/docs/html/api.html
Normal file
26
docs/docs/html/api.html
Normal file
File diff suppressed because one or more lines are too long
@ -10,6 +10,8 @@
|
|||||||
·
|
·
|
||||||
<a href="https://github.com/hay-kot/mealie/issues">Report Bug</a>
|
<a href="https://github.com/hay-kot/mealie/issues">Report Bug</a>
|
||||||
·
|
·
|
||||||
|
<a href="/api/docs">API</a>
|
||||||
|
·
|
||||||
<a href="https://github.com/hay-kot/mealie/issues">
|
<a href="https://github.com/hay-kot/mealie/issues">
|
||||||
Request Feature
|
Request Feature
|
||||||
</a>
|
</a>
|
||||||
|
@ -7,7 +7,6 @@ theme:
|
|||||||
logo: material/silverware-variant
|
logo: material/silverware-variant
|
||||||
features:
|
features:
|
||||||
- navigation.expand
|
- navigation.expand
|
||||||
- navigation.instant
|
|
||||||
|
|
||||||
markdown_extensions:
|
markdown_extensions:
|
||||||
- pymdownx.emoji:
|
- pymdownx.emoji:
|
||||||
@ -35,7 +34,8 @@ nav:
|
|||||||
- Backups and Exports: "getting-started/backups-and-exports.md"
|
- Backups and Exports: "getting-started/backups-and-exports.md"
|
||||||
- Recipe Migration: "getting-started/migration-imports.md"
|
- Recipe Migration: "getting-started/migration-imports.md"
|
||||||
- API Reference:
|
- API Reference:
|
||||||
- Swagger/OpenAPI: "api/api-intro.md"
|
- API Documentation: "api/docs/index.html"
|
||||||
|
- Usage Examples: "api/api-examples.md"
|
||||||
- Contributors Guide:
|
- Contributors Guide:
|
||||||
- Non-Code: "contributors/non-coders.md"
|
- Non-Code: "contributors/non-coders.md"
|
||||||
- Developers Guide:
|
- Developers Guide:
|
||||||
|
5
frontend/.vscode/settings.json
vendored
Normal file
5
frontend/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"cSpell.enableFiletypes": [
|
||||||
|
"!javascript"
|
||||||
|
]
|
||||||
|
}
|
193
frontend/package-lock.json
generated
193
frontend/package-lock.json
generated
@ -1738,6 +1738,16 @@
|
|||||||
"integrity": "sha1-/q7SVZc9LndVW4PbwIhRpsY1IPo=",
|
"integrity": "sha1-/q7SVZc9LndVW4PbwIhRpsY1IPo=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cacache": {
|
"cacache": {
|
||||||
"version": "13.0.1",
|
"version": "13.0.1",
|
||||||
"resolved": "https://registry.npm.taobao.org/cacache/download/cacache-13.0.1.tgz?cache=0&sync_timestamp=1594428402513&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcacache%2Fdownload%2Fcacache-13.0.1.tgz",
|
"resolved": "https://registry.npm.taobao.org/cacache/download/cacache-13.0.1.tgz?cache=0&sync_timestamp=1594428402513&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcacache%2Fdownload%2Fcacache-13.0.1.tgz",
|
||||||
@ -1764,6 +1774,34 @@
|
|||||||
"unique-filename": "^1.1.1"
|
"unique-filename": "^1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"chalk": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"find-cache-dir": {
|
"find-cache-dir": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npm.taobao.org/find-cache-dir/download/find-cache-dir-3.3.1.tgz?cache=0&sync_timestamp=1583735626956&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-cache-dir%2Fdownload%2Ffind-cache-dir-3.3.1.tgz",
|
"resolved": "https://registry.npm.taobao.org/find-cache-dir/download/find-cache-dir-3.3.1.tgz?cache=0&sync_timestamp=1583735626956&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-cache-dir%2Fdownload%2Ffind-cache-dir-3.3.1.tgz",
|
||||||
@ -1785,6 +1823,25 @@
|
|||||||
"path-exists": "^4.0.0"
|
"path-exists": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"loader-utils": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"big.js": "^5.2.2",
|
||||||
|
"emojis-list": "^3.0.0",
|
||||||
|
"json5": "^2.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"locate-path": {
|
"locate-path": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npm.taobao.org/locate-path/download/locate-path-5.0.0.tgz?cache=0&sync_timestamp=1597081764621&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flocate-path%2Fdownload%2Flocate-path-5.0.0.tgz",
|
"resolved": "https://registry.npm.taobao.org/locate-path/download/locate-path-5.0.0.tgz?cache=0&sync_timestamp=1597081764621&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flocate-path%2Fdownload%2Flocate-path-5.0.0.tgz",
|
||||||
@ -1849,6 +1906,16 @@
|
|||||||
"minipass": "^3.1.1"
|
"minipass": "^3.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"terser-webpack-plugin": {
|
"terser-webpack-plugin": {
|
||||||
"version": "2.3.8",
|
"version": "2.3.8",
|
||||||
"resolved": "https://registry.npm.taobao.org/terser-webpack-plugin/download/terser-webpack-plugin-2.3.8.tgz?cache=0&sync_timestamp=1603882075288&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fterser-webpack-plugin%2Fdownload%2Fterser-webpack-plugin-2.3.8.tgz",
|
"resolved": "https://registry.npm.taobao.org/terser-webpack-plugin/download/terser-webpack-plugin-2.3.8.tgz?cache=0&sync_timestamp=1603882075288&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fterser-webpack-plugin%2Fdownload%2Fterser-webpack-plugin-2.3.8.tgz",
|
||||||
@ -1865,6 +1932,18 @@
|
|||||||
"terser": "^4.6.12",
|
"terser": "^4.6.12",
|
||||||
"webpack-sources": "^1.4.3"
|
"webpack-sources": "^1.4.3"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"vue-loader-v16": {
|
||||||
|
"version": "npm:vue-loader@16.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz",
|
||||||
|
"integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"chalk": "^4.1.0",
|
||||||
|
"hash-sum": "^2.0.0",
|
||||||
|
"loader-utils": "^2.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -9082,7 +9161,7 @@
|
|||||||
},
|
},
|
||||||
"rechoir": {
|
"rechoir": {
|
||||||
"version": "0.6.2",
|
"version": "0.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
|
"resolved": "https://registry.npm.taobao.org/rechoir/download/rechoir-0.6.2.tgz",
|
||||||
"integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
|
"integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -9736,6 +9815,11 @@
|
|||||||
"rechoir": "^0.6.2"
|
"rechoir": "^0.6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"shvl": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/shvl/-/shvl-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-VU7R5Uxp38LKHooGuZe0TcX2EPK95nn8DvclAvTPyD9/qHmXvt3dR2pJ4JLZ8uLjxQNQ3zNLFJCreteIj3cvpw=="
|
||||||
|
},
|
||||||
"signal-exit": {
|
"signal-exit": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npm.taobao.org/signal-exit/download/signal-exit-3.0.3.tgz?cache=0&sync_timestamp=1585253323149&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsignal-exit%2Fdownload%2Fsignal-exit-3.0.3.tgz",
|
"resolved": "https://registry.npm.taobao.org/signal-exit/download/signal-exit-3.0.3.tgz?cache=0&sync_timestamp=1585253323149&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsignal-exit%2Fdownload%2Fsignal-exit-3.0.3.tgz",
|
||||||
@ -11065,11 +11149,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vue-cookies": {
|
|
||||||
"version": "1.7.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-cookies/-/vue-cookies-1.7.4.tgz",
|
|
||||||
"integrity": "sha512-mOS5Btr8V9zvAtkmQ7/TfqJIropOx7etDAgBywPCmHjvfJl2gFbH2XgoMghleLoyyMTi5eaJss0mPN7arMoslA=="
|
|
||||||
},
|
|
||||||
"vue-eslint-parser": {
|
"vue-eslint-parser": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npm.taobao.org/vue-eslint-parser/download/vue-eslint-parser-7.1.1.tgz",
|
"resolved": "https://registry.npm.taobao.org/vue-eslint-parser/download/vue-eslint-parser-7.1.1.tgz",
|
||||||
@ -11102,11 +11181,6 @@
|
|||||||
"integrity": "sha1-UylVzB6yCKPZkLOp+acFdGV+CPI=",
|
"integrity": "sha1-UylVzB6yCKPZkLOp+acFdGV+CPI=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"vue-html-to-paper": {
|
|
||||||
"version": "1.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-html-to-paper/-/vue-html-to-paper-1.3.1.tgz",
|
|
||||||
"integrity": "sha512-5IdAPUgStfpVHfcG6nXD0FbUB1onWpvwVD+OZ00jJpy3qaRPkaGD7fFIvYgBB9YPkr0VK065LayEvmGmkkfhaQ=="
|
|
||||||
},
|
|
||||||
"vue-loader": {
|
"vue-loader": {
|
||||||
"version": "15.9.5",
|
"version": "15.9.5",
|
||||||
"resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-15.9.5.tgz?cache=0&sync_timestamp=1605670886675&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-loader%2Fdownload%2Fvue-loader-15.9.5.tgz",
|
"resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-15.9.5.tgz?cache=0&sync_timestamp=1605670886675&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-loader%2Fdownload%2Fvue-loader-15.9.5.tgz",
|
||||||
@ -11128,87 +11202,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vue-loader-v16": {
|
|
||||||
"version": "npm:vue-loader@16.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz",
|
|
||||||
"integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
|
||||||
"chalk": "^4.1.0",
|
|
||||||
"hash-sum": "^2.0.0",
|
|
||||||
"loader-utils": "^2.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-styles": {
|
|
||||||
"version": "4.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
|
||||||
"color-convert": "^2.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"chalk": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
|
||||||
"ansi-styles": "^4.1.0",
|
|
||||||
"supports-color": "^7.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"color-convert": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
|
||||||
"color-name": "~1.1.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"color-name": {
|
|
||||||
"version": "1.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"has-flag": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"loader-utils": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
|
||||||
"big.js": "^5.2.2",
|
|
||||||
"emojis-list": "^3.0.0",
|
|
||||||
"json5": "^2.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"supports-color": {
|
|
||||||
"version": "7.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
|
||||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
|
||||||
"has-flag": "^4.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"vue-router": {
|
"vue-router": {
|
||||||
"version": "3.4.9",
|
"version": "3.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.9.tgz",
|
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.9.tgz",
|
||||||
@ -11268,6 +11261,22 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.0.tgz",
|
||||||
"integrity": "sha512-W74OO2vCJPs9/YjNjW8lLbj+jzT24waTo2KShI8jLvJW8OaIkgb3wuAMA7D+ZiUxDOx3ubwSZTaJBip9G8a3aQ=="
|
"integrity": "sha512-W74OO2vCJPs9/YjNjW8lLbj+jzT24waTo2KShI8jLvJW8OaIkgb3wuAMA7D+ZiUxDOx3ubwSZTaJBip9G8a3aQ=="
|
||||||
},
|
},
|
||||||
|
"vuex-persistedstate": {
|
||||||
|
"version": "4.0.0-beta.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vuex-persistedstate/-/vuex-persistedstate-4.0.0-beta.2.tgz",
|
||||||
|
"integrity": "sha512-JeiweafcU+9d4+/nRvQwK2PyHS9xCRcGIlL2cn0ny/afTw2RP+5M6SdsjkcYoGNICTGPi5i+K3J46ioWEyVgvg==",
|
||||||
|
"requires": {
|
||||||
|
"deepmerge": "^4.2.2",
|
||||||
|
"shvl": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"deepmerge": {
|
||||||
|
"version": "4.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
|
||||||
|
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"watchpack": {
|
"watchpack": {
|
||||||
"version": "1.7.5",
|
"version": "1.7.5",
|
||||||
"resolved": "https://registry.npm.taobao.org/watchpack/download/watchpack-1.7.5.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwatchpack%2Fdownload%2Fwatchpack-1.7.5.tgz",
|
"resolved": "https://registry.npm.taobao.org/watchpack/download/watchpack-1.7.5.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwatchpack%2Fdownload%2Fwatchpack-1.7.5.tgz",
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
"qs": "^6.9.4",
|
"qs": "^6.9.4",
|
||||||
"v-jsoneditor": "^1.4.2",
|
"v-jsoneditor": "^1.4.2",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-html-to-paper": "^1.3.1",
|
|
||||||
"vue-router": "^3.4.9",
|
"vue-router": "^3.4.9",
|
||||||
"vuetify": "^2.4.1",
|
"vuetify": "^2.4.1",
|
||||||
"vuex": "^3.6.0",
|
"vuex": "^3.6.0",
|
||||||
|
@ -16,17 +16,11 @@
|
|||||||
mandatory
|
mandatory
|
||||||
@change="setStoresDarkMode"
|
@change="setStoresDarkMode"
|
||||||
>
|
>
|
||||||
<v-btn value="system">
|
<v-btn value="system"> Default to system </v-btn>
|
||||||
Default to system
|
|
||||||
</v-btn>
|
|
||||||
|
|
||||||
<v-btn value="light">
|
<v-btn value="light"> Light </v-btn>
|
||||||
Light
|
|
||||||
</v-btn>
|
|
||||||
|
|
||||||
<v-btn value="dark">
|
<v-btn value="dark"> Dark </v-btn>
|
||||||
Dark
|
|
||||||
</v-btn>
|
|
||||||
</v-btn-toggle>
|
</v-btn-toggle>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row></v-card-text
|
</v-row></v-card-text
|
||||||
@ -50,7 +44,7 @@
|
|||||||
return-object
|
return-object
|
||||||
v-model="selectedTheme"
|
v-model="selectedTheme"
|
||||||
@change="themeSelected"
|
@change="themeSelected"
|
||||||
:rules="[v => !!v || 'Theme is required']"
|
:rules="[(v) => !!v || 'Theme is required']"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
</v-select>
|
</v-select>
|
||||||
@ -140,19 +134,20 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
Confirmation,
|
Confirmation,
|
||||||
NewTheme
|
NewTheme,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedTheme: {},
|
selectedTheme: {},
|
||||||
selectedDarkMode: "system",
|
selectedDarkMode: "system",
|
||||||
availableThemes: []
|
availableThemes: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
this.availableThemes = await api.themes.requestAll();
|
this.availableThemes = await api.themes.requestAll();
|
||||||
this.selectedTheme = this.$store.getters.getActiveTheme;
|
this.selectedTheme = this.$store.getters.getActiveTheme;
|
||||||
this.selectedDarkMode = this.$store.getters.getDarkMode;
|
this.selectedDarkMode = this.$store.getters.getDarkMode;
|
||||||
|
console.log(this.selectedDarkMode);
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@ -181,7 +176,7 @@ export default {
|
|||||||
//Change to default if deleting current theme.
|
//Change to default if deleting current theme.
|
||||||
if (
|
if (
|
||||||
!this.availableThemes.some(
|
!this.availableThemes.some(
|
||||||
theme => theme.name === this.selectedTheme.name
|
(theme) => theme.name === this.selectedTheme.name
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
await this.$store.dispatch("resetTheme");
|
await this.$store.dispatch("resetTheme");
|
||||||
@ -203,6 +198,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
setStoresDarkMode() {
|
setStoresDarkMode() {
|
||||||
|
console.log(this.selectedDarkMode);
|
||||||
this.$store.commit("setDarkMode", this.selectedDarkMode);
|
this.$store.commit("setDarkMode", this.selectedDarkMode);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@ -216,8 +212,8 @@ export default {
|
|||||||
this.selectedTheme.colors
|
this.selectedTheme.colors
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
198
frontend/src/components/RecipeEditor/PrintRecipe.vue
Normal file
198
frontend/src/components/RecipeEditor/PrintRecipe.vue
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-card flat class="d-print-none">
|
||||||
|
<v-card-text>
|
||||||
|
<v-row align="center" justify="center">
|
||||||
|
<v-btn
|
||||||
|
left
|
||||||
|
color="accent lighten-1 "
|
||||||
|
class="ma-1 image-action"
|
||||||
|
@click="$emit('exit')"
|
||||||
|
>
|
||||||
|
<v-icon> mdi-arrow-left </v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-card flat class="text-center" align-center>
|
||||||
|
<v-card-text>Font Size</v-card-text>
|
||||||
|
<v-card-text>
|
||||||
|
<v-btn
|
||||||
|
class="mx-2"
|
||||||
|
fab
|
||||||
|
dark
|
||||||
|
x-small
|
||||||
|
color="primary"
|
||||||
|
@click="subtractFontSize"
|
||||||
|
>
|
||||||
|
<v-icon dark> mdi-minus </v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
class="mx-2"
|
||||||
|
fab
|
||||||
|
dark
|
||||||
|
x-small
|
||||||
|
color="primary"
|
||||||
|
@click="addFontSize"
|
||||||
|
>
|
||||||
|
<v-icon dark> mdi-plus </v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
<v-card flat>
|
||||||
|
<v-row dense align="center">
|
||||||
|
<v-col md="10" sm="10">
|
||||||
|
<v-card flat>
|
||||||
|
<v-card-title> {{ recipe.name }} </v-card-title>
|
||||||
|
|
||||||
|
<v-card-text> {{ recipe.description }} </v-card-text>
|
||||||
|
|
||||||
|
<v-divider></v-divider>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
<v-col md="1" sm="1" justify-end>
|
||||||
|
<v-img :src="getImage(recipe.image)" max-height="200" max-width="300">
|
||||||
|
</v-img>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
<v-card flat align>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row class="mt-n6">
|
||||||
|
<v-col>
|
||||||
|
<v-btn
|
||||||
|
v-if="recipe.recipeYield"
|
||||||
|
dense
|
||||||
|
small
|
||||||
|
:hover="false"
|
||||||
|
type="label"
|
||||||
|
:ripple="false"
|
||||||
|
elevation="0"
|
||||||
|
color="secondary darken-1"
|
||||||
|
class="rounded-sm static"
|
||||||
|
>
|
||||||
|
{{ recipe.recipeYield }}
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
<v-rating
|
||||||
|
class="mr-2 align-end static"
|
||||||
|
color="secondary darken-1"
|
||||||
|
background-color="secondary lighten-3"
|
||||||
|
length="5"
|
||||||
|
:value="recipe.rating"
|
||||||
|
></v-rating>
|
||||||
|
</v-row>
|
||||||
|
<h2 class="mt-1">Ingredients</h2>
|
||||||
|
<v-row>
|
||||||
|
<v-list dense class="column-wrapper align-start">
|
||||||
|
<v-list-item
|
||||||
|
v-for="(ingredient, index) in recipe.recipeIngredient"
|
||||||
|
:key="generateKey('ingredient', index)"
|
||||||
|
hide-details
|
||||||
|
class="mb-n3 print-text"
|
||||||
|
:label="ingredient"
|
||||||
|
>
|
||||||
|
<v-list-item-icon class="mr-1">
|
||||||
|
<v-icon> mdi-minus </v-icon>
|
||||||
|
</v-list-item-icon>
|
||||||
|
{{ ingredient }}
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-row>
|
||||||
|
<v-row dense>
|
||||||
|
<v-col cols="12">
|
||||||
|
<div v-if="recipe.categories[0]">
|
||||||
|
<h2 class="mt-4">Categories</h2>
|
||||||
|
<v-chip
|
||||||
|
class="ma-1"
|
||||||
|
color="primary"
|
||||||
|
dark
|
||||||
|
v-for="category in recipe.categories"
|
||||||
|
:key="category"
|
||||||
|
>
|
||||||
|
{{ category }}
|
||||||
|
</v-chip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="recipe.tags[0]">
|
||||||
|
<h2 class="mt-4">Tags</h2>
|
||||||
|
<v-chip
|
||||||
|
class="ma-1"
|
||||||
|
color="primary"
|
||||||
|
dark
|
||||||
|
v-for="tag in recipe.tags"
|
||||||
|
:key="tag"
|
||||||
|
>
|
||||||
|
{{ tag }}
|
||||||
|
</v-chip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 v-if="recipe.notes[0]" class="my-2">Notes</h2>
|
||||||
|
<v-card
|
||||||
|
flat
|
||||||
|
class="mt-1"
|
||||||
|
v-for="(note, index) in recipe.notes"
|
||||||
|
:key="generateKey('note', index)"
|
||||||
|
>
|
||||||
|
<v-card-title> {{ note.title }}</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
{{ note.text }}
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12">
|
||||||
|
<h2 class="mb-4">Instructions</h2>
|
||||||
|
|
||||||
|
<v-card
|
||||||
|
v-for="(step, index) in recipe.recipeInstructions"
|
||||||
|
:key="generateKey('step', index)"
|
||||||
|
class="my-n4"
|
||||||
|
flat
|
||||||
|
>
|
||||||
|
<v-card-title class="my-n4">Step: {{ index + 1 }}</v-card-title>
|
||||||
|
<v-card-text class="my-n4">{{ step.text }}</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import utils from "../../utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
recipe: Object,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fontSize: 1.0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getImage(image) {
|
||||||
|
if (image) {
|
||||||
|
return utils.getImageURL(image) + "?rnd=" + this.imageKey;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
generateKey(item, index) {
|
||||||
|
return utils.generateUniqueKey(item, index);
|
||||||
|
},
|
||||||
|
addFontSize() {
|
||||||
|
this.fontSize += 0.2;
|
||||||
|
},
|
||||||
|
subtractFontSize() {
|
||||||
|
this.fontSize -= 0.2;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.column-wrapper {
|
||||||
|
column-count: 2;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,11 +1,22 @@
|
|||||||
|
|
||||||
import api from "../../api";
|
import api from "../../api";
|
||||||
import Vuetify from "../../plugins/vuetify";
|
import Vuetify from "../../plugins/vuetify";
|
||||||
|
|
||||||
|
function inDarkMode(payload) {
|
||||||
|
let isDark;
|
||||||
|
|
||||||
|
if (payload === "system") {
|
||||||
|
//Get System Preference from browser
|
||||||
|
const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
isDark = darkMediaQuery.matches;
|
||||||
|
} else if (payload === "dark") isDark = true;
|
||||||
|
else if (payload === "light") isDark = false;
|
||||||
|
|
||||||
|
return isDark;
|
||||||
|
}
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
activeTheme: {},
|
activeTheme: {},
|
||||||
darkMode: 'system'
|
darkMode: "system",
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
@ -15,17 +26,7 @@ const mutations = {
|
|||||||
state.activeTheme = payload;
|
state.activeTheme = payload;
|
||||||
},
|
},
|
||||||
setDarkMode(state, payload) {
|
setDarkMode(state, payload) {
|
||||||
let isDark;
|
let isDark = inDarkMode(payload);
|
||||||
|
|
||||||
if (payload === 'system') {
|
|
||||||
//Get System Preference from browser
|
|
||||||
const darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
||||||
isDark = darkMediaQuery.matches;
|
|
||||||
}
|
|
||||||
else if (payload === 'dark')
|
|
||||||
isDark = true;
|
|
||||||
else if (payload === 'light')
|
|
||||||
isDark = false;
|
|
||||||
|
|
||||||
if (isDark !== null) {
|
if (isDark !== null) {
|
||||||
Vuetify.framework.theme.dark = isDark;
|
Vuetify.framework.theme.dark = isDark;
|
||||||
@ -40,31 +41,30 @@ const actions = {
|
|||||||
if (defaultTheme.colors) {
|
if (defaultTheme.colors) {
|
||||||
Vuetify.framework.theme.themes.dark = defaultTheme.colors;
|
Vuetify.framework.theme.themes.dark = defaultTheme.colors;
|
||||||
Vuetify.framework.theme.themes.light = defaultTheme.colors;
|
Vuetify.framework.theme.themes.light = defaultTheme.colors;
|
||||||
commit('setTheme', defaultTheme)
|
commit("setTheme", defaultTheme);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async initTheme({ dispatch, getters }) {
|
async initTheme({ dispatch, getters }) {
|
||||||
//If theme is empty resetTheme
|
//If theme is empty resetTheme
|
||||||
if (Object.keys(getters.getActiveTheme).length === 0) {
|
if (Object.keys(getters.getActiveTheme).length === 0) {
|
||||||
await dispatch('resetTheme')
|
await dispatch("resetTheme");
|
||||||
}
|
} else {
|
||||||
else {
|
Vuetify.framework.theme.dark = inDarkMode(getters.getDarkMode);
|
||||||
Vuetify.framework.theme.themes.dark = getters.getActiveTheme.colors;
|
Vuetify.framework.theme.themes.dark = getters.getActiveTheme.colors;
|
||||||
Vuetify.framework.theme.themes.light = getters.getActiveTheme.colors;
|
Vuetify.framework.theme.themes.light = getters.getActiveTheme.colors;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const getters = {
|
const getters = {
|
||||||
getActiveTheme: (state) => state.activeTheme,
|
getActiveTheme: (state) => state.activeTheme,
|
||||||
getDarkMode: (state) => state.darkMode
|
getDarkMode: (state) => state.darkMode,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
state,
|
state,
|
||||||
mutations,
|
mutations,
|
||||||
actions,
|
actions,
|
||||||
getters
|
getters,
|
||||||
}
|
};
|
||||||
|
@ -7,11 +7,13 @@ import userSettings from "./modules/userSettings";
|
|||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
plugins: [createPersistedState({
|
plugins: [
|
||||||
paths: ['userSettings']
|
createPersistedState({
|
||||||
})],
|
paths: ["userSettings"],
|
||||||
|
}),
|
||||||
|
],
|
||||||
modules: {
|
modules: {
|
||||||
userSettings
|
userSettings,
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
// Snackbar
|
// Snackbar
|
||||||
@ -40,7 +42,6 @@ const store = new Vuex.Store({
|
|||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
|
||||||
async requestRecentRecipes() {
|
async requestRecentRecipes() {
|
||||||
const keys = [
|
const keys = [
|
||||||
"name",
|
"name",
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
|
||||||
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
@ -15,21 +14,27 @@ from routes import (
|
|||||||
static_routes,
|
static_routes,
|
||||||
user_routes,
|
user_routes,
|
||||||
)
|
)
|
||||||
from routes.setting_routes import scheduler
|
from routes.setting_routes import scheduler # ! This has to be imported for scheduling
|
||||||
from settings import PORT
|
from settings import PORT, PRODUCTION, docs_url, redoc_url
|
||||||
from utils.logger import logger
|
from utils.logger import logger
|
||||||
|
|
||||||
CWD = Path(__file__).parent
|
CWD = Path(__file__).parent
|
||||||
WEB_PATH = CWD.joinpath("dist")
|
WEB_PATH = CWD.joinpath("dist")
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI(
|
||||||
|
title="Mealie",
|
||||||
|
description="A place for all your recipes",
|
||||||
|
version="0.0.1",
|
||||||
|
docs_url=docs_url,
|
||||||
|
redoc_url=redoc_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Mount Vue Frontend only in production
|
# Mount Vue Frontend only in production
|
||||||
env = os.environ.get("ENV")
|
if PRODUCTION:
|
||||||
if(env == "prod"):
|
|
||||||
app.mount("/static", StaticFiles(directory=WEB_PATH, html=True))
|
app.mount("/static", StaticFiles(directory=WEB_PATH, html=True))
|
||||||
|
|
||||||
|
|
||||||
# API Routes
|
# API Routes
|
||||||
app.include_router(recipe_routes.router)
|
app.include_router(recipe_routes.router)
|
||||||
app.include_router(meal_routes.router)
|
app.include_router(meal_routes.router)
|
||||||
@ -49,6 +54,9 @@ app.include_router(static_routes.router)
|
|||||||
startup.ensure_dirs()
|
startup.ensure_dirs()
|
||||||
startup.generate_default_theme()
|
startup.generate_default_theme()
|
||||||
|
|
||||||
|
# Generate API Documentation
|
||||||
|
if not PRODUCTION:
|
||||||
|
startup.generate_api_docs(app)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logger.info("-----SYSTEM STARTUP-----")
|
logger.info("-----SYSTEM STARTUP-----")
|
||||||
|
@ -1,33 +1,29 @@
|
|||||||
{
|
{
|
||||||
"@context": "http://schema.org",
|
"@context": "http://schema.org",
|
||||||
"@type": "Recipe",
|
"@type": "Recipe",
|
||||||
"articleBody": "Leftover rice is ideal for this dish (and a great way to use up any takeout that\u2019s hanging around), since fully chilled rice tends to be drier and will become crispier and browner in the skillet. To get the best texture, evenly distribute the rice in your pan and gently press down to flatten it out like a pancake. Don\u2019t touch until you hear it crackle! Finish with a sunny-side-up egg\u2014or poach it if you don't mind the stovetop fuss. This recipe is part of the 2021\u00a0Feel Good Food Plan, our eight-day dinner plan for starting the year off right.",
|
"articleBody": "\u201cAfter a draining day juggling work, homeschooling, and urging children to stop using their masks as slingshots, the ideal food for me isn\u2019t perfectly prepared food that\u2019s been tweezered into position, but a meal that\u2019s simply comforting,\u201d writes the Smitten Kitchen\u2019s Deb Perelman. Right now, it\u2019s this deeply cozy pot of tender chicken thighs, jammy leeks, and broth-soaked rice.",
|
||||||
"alternativeHeadline": "To get the best texture, evenly distribute the rice in your pan and gently press down to flatten it. Don\u2019t touch until you hear it crackle!",
|
"alternativeHeadline": "This one-skillet dinner gets deep oniony flavor from lots of leeks cooked down to jammy tenderness.",
|
||||||
"dateModified": "2021-01-03 03:40:32.190000",
|
"dateModified": "2021-01-06 17:07:07.791000",
|
||||||
"datePublished": "2021-01-01 06:00:00",
|
"datePublished": "2020-08-18 04:00:00",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"recipes",
|
"recipes",
|
||||||
"healthyish",
|
"chicken recipes",
|
||||||
"salad",
|
|
||||||
"ginger",
|
|
||||||
"garlic",
|
|
||||||
"orange",
|
|
||||||
"oil",
|
|
||||||
"soy sauce",
|
|
||||||
"lemon juice",
|
|
||||||
"sesame oil",
|
|
||||||
"kosher salt",
|
"kosher salt",
|
||||||
"broccoli",
|
"black pepper",
|
||||||
"brown rice",
|
"butter",
|
||||||
"egg",
|
"leek",
|
||||||
"celery",
|
"lemon zest",
|
||||||
"cilantro",
|
"rice",
|
||||||
"mint",
|
"chicken broth",
|
||||||
"feel good food plan 2021",
|
"anchovy",
|
||||||
"feel good food plan",
|
"garlic",
|
||||||
|
"capers",
|
||||||
|
"herb",
|
||||||
|
"olive oil",
|
||||||
|
"healthyish",
|
||||||
"web"
|
"web"
|
||||||
],
|
],
|
||||||
"thumbnailUrl": "https://assets.bonappetit.com/photos/5fdbe70a84d333dd1dcc7900/1:1/w_1698,h_1698,c_limit/BA1220feelgoodalt.jpg",
|
"thumbnailUrl": "https://assets.bonappetit.com/photos/5f29796456f43685a49327fb/1:1/w_1125,h_1125,c_limit/Chicken-and-Rice-With-Leeks-Salsa-Verde-01.jpg",
|
||||||
"publisher": {
|
"publisher": {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "Organization",
|
"@type": "Organization",
|
||||||
@ -51,75 +47,52 @@
|
|||||||
"author": [
|
"author": [
|
||||||
{
|
{
|
||||||
"@type": "Person",
|
"@type": "Person",
|
||||||
"name": "Devonn Francis",
|
"name": "Deb Perelman",
|
||||||
"sameAs": "https://bon-appetit.com/contributor/devonn-francis/"
|
"sameAs": "https://bon-appetit.com/contributor/deb-perelman/"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"aggregateRating": {
|
"aggregateRating": {
|
||||||
"@type": "AggregateRating",
|
"@type": "AggregateRating",
|
||||||
"ratingValue": 4,
|
"ratingValue": 4,
|
||||||
"ratingCount": 2
|
"ratingCount": 47
|
||||||
},
|
},
|
||||||
"description": "To get the best texture, evenly distribute the rice in your pan and gently press down to flatten it. Don\u2019t touch until you hear it crackle! ",
|
"description": "This one-skillet dinner gets deep oniony flavor from lots of leeks cooked down to jammy tenderness.",
|
||||||
"image": "crispy-rice-with-ginger-citrus-celery-salad.jpg",
|
"image": "chicken-and-rice-with-leeks-and-salsa-verde.jpg",
|
||||||
"name": "Crispy Rice With Ginger-Citrus Celery Salad",
|
"headline": "Chicken and Rice With Leeks and Salsa Verde",
|
||||||
|
"name": "Chicken and Rice With Leeks and Salsa Verde",
|
||||||
"recipeIngredient": [
|
"recipeIngredient": [
|
||||||
"1 2\" piece ginger, peeled, finely grated",
|
"1\u00bd lb. skinless, boneless chicken thighs (4\u20138 depending on size)",
|
||||||
"1 small garlic clove, finely grated",
|
"Kosher salt, freshly ground pepper",
|
||||||
"Juice of 1 orange",
|
"3 Tbsp. unsalted butter, divided",
|
||||||
"2 tbsp. vegetable oil",
|
"2 large or 3 medium leeks, white and pale green parts only, halved lengthwise, thinly sliced",
|
||||||
"1Tbsp. coconut aminos or low-sodium soy sauce",
|
"Zest and juice of 1 lemon, divided",
|
||||||
"1 Tbsp. fresh lemon juice",
|
"1\u00bd cups long-grain white rice, rinsed until water runs clear",
|
||||||
"\u00bc tsp. toasted sesame oil",
|
"2\u00be cups low-sodium chicken broth",
|
||||||
"Kosher salt",
|
"1 oil-packed anchovy fillet",
|
||||||
"1 medium head of broccoli",
|
"2 garlic cloves",
|
||||||
"6 Tbsp. (or more) vegetable oil, divided",
|
"1 Tbsp. drained capers",
|
||||||
"Kosher salt",
|
"Crushed red pepper flakes",
|
||||||
"2 cups chilled cooked brown rice",
|
"1 cup tender herb leaves (such as parsley, cilantro, and/or mint)",
|
||||||
"4 large eggs",
|
"4\u20135 Tbsp. extra-virgin olive oil"
|
||||||
"3 celery stalks, thinly sliced on a steep diagonal",
|
|
||||||
"\u00bd cup cilantro leaves with tender stems",
|
|
||||||
"\u00bd cup mint leaves",
|
|
||||||
"Crushed red pepper flakes (for serving)"
|
|
||||||
],
|
],
|
||||||
"recipeInstructions": [
|
"recipeInstructions": [
|
||||||
{
|
{
|
||||||
"@type": "HowToStep",
|
"@type": "HowToStep",
|
||||||
"text": "Whisk ginger, garlic, orange juice, vegetable oil, coconut aminos, lemon juice, and sesame oil in a small bowl; season with salt and set aside."
|
"text": "Season chicken with salt and pepper. Melt 2 Tbsp. butter in a large high-sided skillet over medium-high heat. Add leeks and half of lemon zest, season with salt and pepper, and mix to coat leeks in butter. Reduce heat to medium-low, cover, and cook, stirring occasionally, until leeks are somewhat tender, about 5 minutes. Remove lid, increase heat to medium-high, and cook, stirring occasionally, until tender and just starting to take on color, about 3 minutes. Add rice and cook, stirring often, 3 minutes, then add broth, scraping up any browned bits. Tuck short sides of each chicken thigh underneath so they are touching and nestle seam side down into rice mixture. Bring to a simmer. Cover, reduce heat to medium-low, and cook until rice is tender and chicken is cooked through, about 20 minutes. Remove from heat. Cut remaining 1 Tbsp. butter into small pieces and scatter over mixture. Re-cover and let sit 10 minutes."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"@type": "HowToStep",
|
"@type": "HowToStep",
|
||||||
"text": "Trim about \u00bd\" from woody end of broccoli stem. Peel tough outer layer from stem. Cut florets from stems and thinly slice stems about \u00bd\" thick. Break florets apart with your hands into 1\"\u20131\u00bd\" pieces."
|
"text": "Meanwhile, pulse anchovy, garlic, capers, a few pinches of red pepper flakes, and remaining lemon zest in a food processor until finely chopped. Add herbs; process until a paste forms. With motor running, gradually stream in oil until loosened to a thick sauce. Add half of lemon juice; season salsa verde with salt."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"@type": "HowToStep",
|
"@type": "HowToStep",
|
||||||
"text": "Heat 2 Tbsp. oil in a large nonstick skillet over medium. Working in 2 batches if needed, arrange broccoli in a single layer and cook, tossing occasionally, until broccoli is bright green and lightly charred around the edges, about\u00a03 minutes. Transfer to a large plate."
|
"text": "Drizzle remaining lemon juice over chicken and rice. Serve with salsa verde."
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "HowToStep",
|
|
||||||
"text": "Pour 2 Tbsp. oil into same pan and heat over medium-high. Once you see the first wisp of smoke, add rice and season lightly with salt. Using a spatula or spoon, press rice evenly into pan like a pancake. Rice will begin to crackle, but don\u2019t fuss with it. When the crackling has died down almost completely, about\u00a03 minutes, break rice into large pieces and turn over."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "HowToStep",
|
|
||||||
"text": "Add broccoli back to pan and give everything a toss to combine. Cook, tossing occasionally and adding another\u00a01 Tbsp. oil if pan looks dry, until broccoli is tender and rice is warmed through and very crisp, about 5 minutes. Transfer mixture to a platter or divide among plates and set aside."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "HowToStep",
|
|
||||||
"text": "Wipe out skillet; heat remaining\u00a02 Tbsp. oil over medium-high. Crack eggs into skillet; season with salt. Oil should bubble around eggs right away. Cook, rotating skillet occasionally, until whites are golden brown and crisp at the edges and set around the yolk (which should be runny), about 2 minutes."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "HowToStep",
|
|
||||||
"text": "Toss celery, cilantro, and mint with\u00a03 Tbsp. reserved dressing and a pinch of salt in a medium bowl to combine."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "HowToStep",
|
|
||||||
"text": "Scatter celery salad over fried rice; top with fried eggs and sprinkle with red pepper flakes. Serve extra dressing alongside."
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"recipeYield": "4 servings",
|
"recipeYield": "4 Servings",
|
||||||
"url": "https://www.bonappetit.com/recipe/crispy-rice-with-ginger-citrus-celery-salad",
|
"url": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde",
|
||||||
"slug": "crispy-rice-with-ginger-citrus-celery-salad",
|
"slug": "chicken-and-rice-with-leeks-and-salsa-verde",
|
||||||
"orgURL": "https://www.bonappetit.com/recipe/crispy-rice-with-ginger-citrus-celery-salad",
|
"orgURL": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde",
|
||||||
"categories": [],
|
"categories": [],
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"dateAdded": null,
|
"dateAdded": null,
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 1002 KiB |
Binary file not shown.
Before Width: | Height: | Size: 622 KiB |
@ -1,5 +1,4 @@
|
|||||||
# from datetime import datetime
|
from typing import List, Optional
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@ -7,3 +6,24 @@ from pydantic import BaseModel
|
|||||||
class BackupJob(BaseModel):
|
class BackupJob(BaseModel):
|
||||||
tag: Optional[str]
|
tag: Optional[str]
|
||||||
template: Optional[str]
|
template: Optional[str]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"tag": "July 23rd 2021",
|
||||||
|
"template": "recipes.md",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Imports(BaseModel):
|
||||||
|
imports: List[str]
|
||||||
|
templates: List[str]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"imports": ["sample_data.zip", "sampe_data2.zip"],
|
||||||
|
"templates": ["recipes.md", "custom_template.md"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
12
mealie/models/migration_models.py
Normal file
12
mealie/models/migration_models.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from pydantic.main import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class ChowdownURL(BaseModel):
|
||||||
|
url: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"url": "https://chowdownrepo.com/repo",
|
||||||
|
}
|
||||||
|
}
|
59
mealie/models/recipe_models.py
Normal file
59
mealie/models/recipe_models.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
import pydantic
|
||||||
|
from pydantic.main import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class RecipeResponse(BaseModel):
|
||||||
|
List
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": [
|
||||||
|
{
|
||||||
|
"slug": "crockpot-buffalo-chicken",
|
||||||
|
"image": "crockpot-buffalo-chicken.jpg",
|
||||||
|
"name": "Crockpot Buffalo Chicken",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "downtown-marinade",
|
||||||
|
"image": "downtown-marinade.jpg",
|
||||||
|
"name": "Downtown Marinade",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "detroit-style-pepperoni-pizza",
|
||||||
|
"image": "detroit-style-pepperoni-pizza.jpg",
|
||||||
|
"name": "Detroit-Style Pepperoni Pizza",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "crispy-carrots",
|
||||||
|
"image": "crispy-carrots.jpg",
|
||||||
|
"name": "Crispy Carrots",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AllRecipeRequest(BaseModel):
|
||||||
|
properties: List[str]
|
||||||
|
limit: Optional[int]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"properties": ["name", "slug", "image"],
|
||||||
|
"limit": 100,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RecipeURLIn(BaseModel):
|
||||||
|
url: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {"example": {"url": "https://myfavoriterecipes.com/recipes"}}
|
||||||
|
|
||||||
|
|
||||||
|
class SlugResponse(BaseModel):
|
||||||
|
class Config:
|
||||||
|
schema_extra = {"example": "adult-mac-and-cheese"}
|
@ -1,14 +1,20 @@
|
|||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from models.backup_models import BackupJob
|
from models.backup_models import BackupJob, Imports
|
||||||
from services.backup_services import (BACKUP_DIR, TEMPLATE_DIR, export_db,
|
from pydantic.main import BaseModel
|
||||||
import_from_archive)
|
from services.backup_services import (
|
||||||
|
BACKUP_DIR,
|
||||||
|
TEMPLATE_DIR,
|
||||||
|
export_db,
|
||||||
|
import_from_archive,
|
||||||
|
)
|
||||||
from utils.snackbar import SnackResponse
|
from utils.snackbar import SnackResponse
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/backups/available/", tags=["Import / Export"])
|
@router.get("/api/backups/available/", tags=["Import / Export"], response_model=Imports)
|
||||||
async def available_imports():
|
async def available_imports():
|
||||||
|
"""Returns a list of avaiable .zip files for import into Mealie."""
|
||||||
imports = []
|
imports = []
|
||||||
templates = []
|
templates = []
|
||||||
for archive in BACKUP_DIR.glob("*.zip"):
|
for archive in BACKUP_DIR.glob("*.zip"):
|
||||||
@ -17,12 +23,12 @@ async def available_imports():
|
|||||||
for template in TEMPLATE_DIR.glob("*.md"):
|
for template in TEMPLATE_DIR.glob("*.md"):
|
||||||
templates.append(template.name)
|
templates.append(template.name)
|
||||||
|
|
||||||
return {"imports": imports, "templates": templates}
|
return Imports(imports=imports, templates=templates)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/backups/export/database/", tags=["Import / Export"], status_code=201)
|
@router.post("/api/backups/export/database/", tags=["Import / Export"], status_code=201)
|
||||||
async def export_database(data: BackupJob):
|
async def export_database(data: BackupJob):
|
||||||
|
"""Generates a backup of the recipe database in json format."""
|
||||||
try:
|
try:
|
||||||
export_path = export_db(data.tag, data.template)
|
export_path = export_db(data.tag, data.template)
|
||||||
except:
|
except:
|
||||||
@ -38,6 +44,7 @@ async def export_database(data: BackupJob):
|
|||||||
"/api/backups/{file_name}/import/", tags=["Import / Export"], status_code=200
|
"/api/backups/{file_name}/import/", tags=["Import / Export"], status_code=200
|
||||||
)
|
)
|
||||||
async def import_database(file_name: str):
|
async def import_database(file_name: str):
|
||||||
|
""" Import a database backup file generated from Mealie. """
|
||||||
imported = import_from_archive(file_name)
|
imported = import_from_archive(file_name)
|
||||||
return imported
|
return imported
|
||||||
|
|
||||||
@ -48,6 +55,7 @@ async def import_database(file_name: str):
|
|||||||
status_code=200,
|
status_code=200,
|
||||||
)
|
)
|
||||||
async def delete_backup(backup_name: str):
|
async def delete_backup(backup_name: str):
|
||||||
|
""" Removes a database backup from the file system """
|
||||||
|
|
||||||
try:
|
try:
|
||||||
BACKUP_DIR.joinpath(backup_name).unlink()
|
BACKUP_DIR.joinpath(backup_name).unlink()
|
||||||
|
@ -1,28 +1,26 @@
|
|||||||
from pprint import pprint
|
from typing import List
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from models.recipe_models import SlugResponse
|
||||||
from services.meal_services import MealPlan
|
from services.meal_services import MealPlan
|
||||||
from utils.snackbar import SnackResponse
|
from utils.snackbar import SnackResponse
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/meal-plan/all/", tags=["Meal Plan"])
|
@router.get("/api/meal-plan/all/", tags=["Meal Plan"], response_model=List[MealPlan])
|
||||||
async def get_all_meals():
|
async def get_all_meals():
|
||||||
""" Returns a list of all available meal plans """
|
""" Returns a list of all available Meal Plan """
|
||||||
|
|
||||||
return MealPlan.get_all()
|
return MealPlan.get_all()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/meal-plan/create/", tags=["Meal Plan"])
|
@router.post("/api/meal-plan/create/", tags=["Meal Plan"])
|
||||||
async def set_meal_plan(data: MealPlan):
|
async def set_meal_plan(data: MealPlan):
|
||||||
""" Creates Mealplan from Frontend Data"""
|
""" Creates a meal plan database entry """
|
||||||
data.process_meals()
|
data.process_meals()
|
||||||
data.save_to_db()
|
data.save_to_db()
|
||||||
|
|
||||||
# try:
|
|
||||||
|
|
||||||
# except:
|
|
||||||
# raise HTTPException(
|
# raise HTTPException(
|
||||||
# status_code=404,
|
# status_code=404,
|
||||||
# detail=SnackResponse.error("Unable to Create Mealplan See Log"),
|
# detail=SnackResponse.error("Unable to Create Mealplan See Log"),
|
||||||
@ -33,7 +31,7 @@ async def set_meal_plan(data: MealPlan):
|
|||||||
|
|
||||||
@router.post("/api/meal-plan/{plan_id}/update/", tags=["Meal Plan"])
|
@router.post("/api/meal-plan/{plan_id}/update/", tags=["Meal Plan"])
|
||||||
async def update_meal_plan(plan_id: str, meal_plan: MealPlan):
|
async def update_meal_plan(plan_id: str, meal_plan: MealPlan):
|
||||||
""" Updates a Meal Plan Based off ID """
|
""" Updates a meal plan based off ID """
|
||||||
|
|
||||||
try:
|
try:
|
||||||
meal_plan.process_meals()
|
meal_plan.process_meals()
|
||||||
@ -49,21 +47,27 @@ async def update_meal_plan(plan_id: str, meal_plan: MealPlan):
|
|||||||
|
|
||||||
@router.delete("/api/meal-plan/{plan_id}/delete/", tags=["Meal Plan"])
|
@router.delete("/api/meal-plan/{plan_id}/delete/", tags=["Meal Plan"])
|
||||||
async def delete_meal_plan(plan_id):
|
async def delete_meal_plan(plan_id):
|
||||||
""" Doc Str """
|
""" Removes a meal plan from the database """
|
||||||
|
|
||||||
MealPlan.delete(plan_id)
|
MealPlan.delete(plan_id)
|
||||||
|
|
||||||
return SnackResponse.success("Mealplan Deleted")
|
return SnackResponse.success("Mealplan Deleted")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/meal-plan/today/", tags=["Meal Plan"])
|
@router.get(
|
||||||
|
"/api/meal-plan/today/",
|
||||||
|
tags=["Meal Plan"],
|
||||||
|
)
|
||||||
async def get_today():
|
async def get_today():
|
||||||
""" Returns the meal plan data for today """
|
"""
|
||||||
|
Returns the recipe slug for the meal scheduled for today.
|
||||||
|
If no meal is scheduled nothing is returned
|
||||||
|
"""
|
||||||
|
|
||||||
return MealPlan.today()
|
return MealPlan.today()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/meal-plan/this-week/", tags=["Meal Plan"])
|
@router.get("/api/meal-plan/this-week/", tags=["Meal Plan"], response_model=MealPlan)
|
||||||
async def get_this_week():
|
async def get_this_week():
|
||||||
""" Returns the meal plan data for this week """
|
""" Returns the meal plan data for this week """
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from models.backup_models import BackupJob
|
from models.backup_models import BackupJob
|
||||||
|
from models.migration_models import ChowdownURL
|
||||||
from services.migrations.chowdown import chowdown_migrate as chowdow_migrate
|
from services.migrations.chowdown import chowdown_migrate as chowdow_migrate
|
||||||
from utils.snackbar import SnackResponse
|
from utils.snackbar import SnackResponse
|
||||||
|
|
||||||
@ -7,10 +8,10 @@ router = APIRouter()
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/api/migration/chowdown/repo/", tags=["Migration"])
|
@router.post("/api/migration/chowdown/repo/", tags=["Migration"])
|
||||||
async def import_chowdown_recipes(repo: dict):
|
async def import_chowdown_recipes(repo: ChowdownURL):
|
||||||
""" Import Chowsdown Recipes from Repo URL """
|
""" Import Chowsdown Recipes from Repo URL """
|
||||||
try:
|
try:
|
||||||
report = chowdow_migrate(repo.get("url"))
|
report = chowdow_migrate(repo.url)
|
||||||
return SnackResponse.success(
|
return SnackResponse.success(
|
||||||
"Recipes Imported from Git Repo, see report for failures.",
|
"Recipes Imported from Git Repo, see report for failures.",
|
||||||
additional_data=report,
|
additional_data=report,
|
||||||
|
@ -2,6 +2,7 @@ from typing import List, Optional
|
|||||||
|
|
||||||
from fastapi import APIRouter, File, Form, HTTPException, Query
|
from fastapi import APIRouter, File, Form, HTTPException, Query
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
|
from models.recipe_models import AllRecipeRequest, RecipeURLIn, SlugResponse
|
||||||
from services.image_services import read_image, write_image
|
from services.image_services import read_image, write_image
|
||||||
from services.recipe_services import Recipe, read_requested_values
|
from services.recipe_services import Recipe, read_requested_values
|
||||||
from services.scrape_services import create_from_url
|
from services.scrape_services import create_from_url
|
||||||
@ -10,17 +11,42 @@ from utils.snackbar import SnackResponse
|
|||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/all-recipes/", tags=["Recipes"])
|
@router.get("/api/all-recipes/", tags=["Recipes"], response_model=List[dict])
|
||||||
async def get_all_recipes(
|
async def get_all_recipes(
|
||||||
keys: Optional[List[str]] = Query(...), num: Optional[int] = 100
|
keys: Optional[List[str]] = Query(...), num: Optional[int] = 100
|
||||||
) -> Optional[List[str]]:
|
):
|
||||||
""" Returns key data for all recipes """
|
"""
|
||||||
|
Returns key data for all recipes based off the query paramters provided.
|
||||||
|
For example, if slug, image, and name are provided you will recieve a list of
|
||||||
|
recipes containing the slug, image, and name property. By default, responses
|
||||||
|
are limited to 100.
|
||||||
|
|
||||||
|
**Note:** You may experience problems with with query parameters. As an alternative
|
||||||
|
you may also use the post method and provide a body.
|
||||||
|
See the *Post* method for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
all_recipes = read_requested_values(keys, num)
|
all_recipes = read_requested_values(keys, num)
|
||||||
return all_recipes
|
return all_recipes
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/recipe/{recipe_slug}/", tags=["Recipes"])
|
@router.post("/api/all-recipes/", tags=["Recipes"], response_model=List[dict])
|
||||||
|
async def get_all_recipes_post(body: AllRecipeRequest):
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
recipes containing the slug, image, and name property.
|
||||||
|
|
||||||
|
Refer to the body example for data formats.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
all_recipes = read_requested_values(body.properties, body.limit)
|
||||||
|
|
||||||
|
return all_recipes
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/recipe/{recipe_slug}/", tags=["Recipes"], response_model=Recipe)
|
||||||
async def get_recipe(recipe_slug: str):
|
async def get_recipe(recipe_slug: str):
|
||||||
""" Takes in a recipe slug, returns all data for a recipe """
|
""" Takes in a recipe slug, returns all data for a recipe """
|
||||||
recipe = Recipe.get_by_slug(recipe_slug)
|
recipe = Recipe.get_by_slug(recipe_slug)
|
||||||
@ -37,24 +63,21 @@ async def get_recipe_img(recipe_slug: str):
|
|||||||
|
|
||||||
|
|
||||||
# Recipe Creations
|
# Recipe Creations
|
||||||
@router.post("/api/recipe/create-url/", tags=["Recipes"], status_code=201)
|
@router.post(
|
||||||
async def get_recipe_url(url: dict):
|
"/api/recipe/create-url/",
|
||||||
""" Takes in a URL and Attempts to scrape data and load it into the database """
|
tags=["Recipes"],
|
||||||
|
status_code=201,
|
||||||
|
response_model=str,
|
||||||
|
)
|
||||||
|
async def parse_recipe_url(url: RecipeURLIn):
|
||||||
|
""" Takes in a URL and attempts to scrape data and load it into the database """
|
||||||
|
|
||||||
url = url.get("url")
|
slug = create_from_url(url.url)
|
||||||
slug = create_from_url(url)
|
|
||||||
|
|
||||||
# try:
|
|
||||||
# slug = create_from_url(url)
|
|
||||||
# except:
|
|
||||||
# raise HTTPException(
|
|
||||||
# status_code=400, detail=SnackResponse.error("Unable to Parse URL")
|
|
||||||
# )
|
|
||||||
|
|
||||||
return slug
|
return slug
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/recipe/create/", tags=["Recipes"])
|
@router.post("/api/recipe/create/", tags=["Recipes"], response_model=SlugResponse)
|
||||||
async def create_from_json(data: Recipe) -> str:
|
async def create_from_json(data: Recipe) -> str:
|
||||||
""" Takes in a JSON string and loads data into the database as a new entry"""
|
""" Takes in a JSON string and loads data into the database as a new entry"""
|
||||||
created_recipe = data.save_to_db()
|
created_recipe = data.save_to_db()
|
||||||
@ -63,7 +86,7 @@ async def create_from_json(data: Recipe) -> str:
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/api/recipe/{recipe_slug}/update/image/", tags=["Recipes"])
|
@router.post("/api/recipe/{recipe_slug}/update/image/", tags=["Recipes"])
|
||||||
def update_image(
|
def update_recipe_image(
|
||||||
recipe_slug: str, image: bytes = File(...), extension: str = Form(...)
|
recipe_slug: str, image: bytes = File(...), extension: str = Form(...)
|
||||||
):
|
):
|
||||||
""" Removes an existing image and replaces it with the incoming file. """
|
""" Removes an existing image and replaces it with the incoming file. """
|
||||||
@ -73,7 +96,7 @@ def update_image(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/api/recipe/{recipe_slug}/update/", tags=["Recipes"])
|
@router.post("/api/recipe/{recipe_slug}/update/", tags=["Recipes"])
|
||||||
async def update(recipe_slug: str, data: Recipe):
|
async def update_recipe(recipe_slug: str, data: Recipe):
|
||||||
""" Updates a recipe by existing slug and data. Data should containt """
|
""" Updates a recipe by existing slug and data. Data should containt """
|
||||||
|
|
||||||
data.update(recipe_slug)
|
data.update(recipe_slug)
|
||||||
@ -82,7 +105,7 @@ async def update(recipe_slug: str, data: Recipe):
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/api/recipe/{recipe_slug}/delete/", tags=["Recipes"])
|
@router.delete("/api/recipe/{recipe_slug}/delete/", tags=["Recipes"])
|
||||||
async def delete(recipe_slug: str):
|
async def delete_recipe(recipe_slug: str):
|
||||||
""" Deletes a recipe by slug """
|
""" Deletes a recipe by slug """
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
from db.mongo_setup import global_init
|
from db.mongo_setup import global_init
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from services.scheduler_services import Scheduler, post_webhooks
|
from services.scheduler_services import Scheduler, post_webhooks
|
||||||
@ -13,14 +15,14 @@ scheduler.startup_scheduler()
|
|||||||
|
|
||||||
@router.get("/api/site-settings/", tags=["Settings"])
|
@router.get("/api/site-settings/", tags=["Settings"])
|
||||||
async def get_main_settings():
|
async def get_main_settings():
|
||||||
""" Returns basic site Settings """
|
""" Returns basic site settings """
|
||||||
|
|
||||||
return SiteSettings.get_site_settings()
|
return SiteSettings.get_site_settings()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/site-settings/webhooks/test/", tags=["Settings"])
|
@router.post("/api/site-settings/webhooks/test/", tags=["Settings"])
|
||||||
async def test_webhooks():
|
async def test_webhooks():
|
||||||
""" Test Webhooks """
|
""" Run the function to test your webhooks """
|
||||||
|
|
||||||
return post_webhooks()
|
return post_webhooks()
|
||||||
|
|
||||||
@ -40,22 +42,26 @@ async def update_settings(data: SiteSettings):
|
|||||||
return SnackResponse.success("Settings Updated")
|
return SnackResponse.success("Settings Updated")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/site-settings/themes/", tags=["Themes"])
|
@router.get(
|
||||||
|
"/api/site-settings/themes/", tags=["Themes"]
|
||||||
|
)
|
||||||
async def get_all_themes():
|
async def get_all_themes():
|
||||||
""" Returns all site themes """
|
""" Returns all site themes """
|
||||||
|
|
||||||
return SiteTheme.get_all()
|
return SiteTheme.get_all()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/site-settings/themes/{theme_name}/", tags=["Themes"])
|
@router.get(
|
||||||
|
"/api/site-settings/themes/{theme_name}/", tags=["Themes"]
|
||||||
|
)
|
||||||
async def get_single_theme(theme_name: str):
|
async def get_single_theme(theme_name: str):
|
||||||
""" Returns basic site Settings """
|
""" Returns a named theme """
|
||||||
return SiteTheme.get_by_name(theme_name)
|
return SiteTheme.get_by_name(theme_name)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/site-settings/themes/create/", tags=["Themes"])
|
@router.post("/api/site-settings/themes/create/", tags=["Themes"])
|
||||||
async def create_theme(data: SiteTheme):
|
async def create_theme(data: SiteTheme):
|
||||||
""" Creates a Site Color Theme """
|
""" Creates a site color theme database entry """
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data.save_to_db()
|
data.save_to_db()
|
||||||
@ -69,7 +75,7 @@ async def create_theme(data: SiteTheme):
|
|||||||
|
|
||||||
@router.post("/api/site-settings/themes/{theme_name}/update/", tags=["Themes"])
|
@router.post("/api/site-settings/themes/{theme_name}/update/", tags=["Themes"])
|
||||||
async def update_theme(theme_name: str, data: SiteTheme):
|
async def update_theme(theme_name: str, data: SiteTheme):
|
||||||
""" Returns basic site Settings """
|
""" Update a theme database entry """
|
||||||
try:
|
try:
|
||||||
data.update_document()
|
data.update_document()
|
||||||
except:
|
except:
|
||||||
@ -82,7 +88,7 @@ async def update_theme(theme_name: str, data: SiteTheme):
|
|||||||
|
|
||||||
@router.delete("/api/site-settings/themes/{theme_name}/delete/", tags=["Themes"])
|
@router.delete("/api/site-settings/themes/{theme_name}/delete/", tags=["Themes"])
|
||||||
async def delete_theme(theme_name: str):
|
async def delete_theme(theme_name: str):
|
||||||
""" Returns basic site Settings """
|
""" Deletes theme from the database """
|
||||||
try:
|
try:
|
||||||
SiteTheme.delete_theme(theme_name)
|
SiteTheme.delete_theme(theme_name)
|
||||||
except:
|
except:
|
||||||
|
@ -24,6 +24,18 @@ class SiteSettings(BaseModel):
|
|||||||
name: str = "main"
|
name: str = "main"
|
||||||
webhooks: Webhooks
|
webhooks: Webhooks
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"name": "main",
|
||||||
|
"webhooks": {
|
||||||
|
"webhookTime": "00:00",
|
||||||
|
"webhookURLs": ["https://mywebhookurl.com/webhook"],
|
||||||
|
"enable": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _unpack_doc(document: SiteSettingsDocument):
|
def _unpack_doc(document: SiteSettingsDocument):
|
||||||
document = json.loads(document.to_json())
|
document = json.loads(document.to_json())
|
||||||
@ -65,6 +77,22 @@ class SiteTheme(BaseModel):
|
|||||||
name: str
|
name: str
|
||||||
colors: Colors
|
colors: Colors
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"name": "default",
|
||||||
|
"colors": {
|
||||||
|
"primary": "#E58325",
|
||||||
|
"accent": "#00457A",
|
||||||
|
"secondary": "#973542",
|
||||||
|
"success": "#5AB1BB",
|
||||||
|
"info": "#4990BA",
|
||||||
|
"warning": "#FF4081",
|
||||||
|
"error": "#EF5350",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_by_name(theme_name):
|
def get_by_name(theme_name):
|
||||||
document = SiteThemeDocument.objects.get(name=theme_name)
|
document = SiteThemeDocument.objects.get(name=theme_name)
|
||||||
|
@ -8,7 +8,16 @@ ENV = CWD.joinpath(".env")
|
|||||||
dotenv.load_dotenv(ENV)
|
dotenv.load_dotenv(ENV)
|
||||||
|
|
||||||
# General
|
# General
|
||||||
|
PRODUCTION = os.environ.get("ENV")
|
||||||
PORT = int(os.getenv("mealie_port", 9000))
|
PORT = int(os.getenv("mealie_port", 9000))
|
||||||
|
API = os.getenv("api_docs", True)
|
||||||
|
|
||||||
|
if API:
|
||||||
|
docs_url = "/docs"
|
||||||
|
redoc_url = "/redoc"
|
||||||
|
else:
|
||||||
|
docs_url = None
|
||||||
|
redoc_url = None
|
||||||
|
|
||||||
# Mongo Database
|
# Mongo Database
|
||||||
MEALIE_DB_NAME = os.getenv("mealie_db_name", "mealie")
|
MEALIE_DB_NAME = os.getenv("mealie_db_name", "mealie")
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from services.settings_services import Colors, SiteTheme
|
from services.settings_services import Colors, SiteTheme
|
||||||
@ -37,5 +38,43 @@ def generate_default_theme():
|
|||||||
default_theme.save_to_db()
|
default_theme.save_to_db()
|
||||||
|
|
||||||
|
|
||||||
|
"""Script to export the ReDoc documentation page into a standalone HTML file."""
|
||||||
|
|
||||||
|
HTML_TEMPLATE = """<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||||
|
<title>My Project - ReDoc</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style data-styled="" data-styled-version="4.4.1"></style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="redoc-container"></div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js"> </script>
|
||||||
|
<script>
|
||||||
|
var spec = %s;
|
||||||
|
Redoc.init(spec, {}, document.getElementById("redoc-container"));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
CWD = Path(__file__).parent
|
||||||
|
out_path = CWD.joinpath("temp", "index.html")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_api_docs(app):
|
||||||
|
with open(out_path, "w") as fd:
|
||||||
|
print(HTML_TEMPLATE % json.dumps(app.openapi()), file=fd)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
pass
|
pass
|
||||||
|
@ -1 +0,0 @@
|
|||||||
import datetime
|
|
@ -1 +0,0 @@
|
|||||||
// Test Notify
|
|
Loading…
x
Reference in New Issue
Block a user