mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-03-04 12:43:13 -05:00
v0.4.0 (#236)
* Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog * Dropping Mongo From Dev Branch (#89) * Fix link to Docker Hub Found an extra s. DESTROYED it. * initial pass * second pass cleanup * backup card framework * backup card functionality * translation * upload button vile creation * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * dev bug: change data location to prevent reloads * api docs * api docs bug * workflow update Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * Add French Translation (#93) * New tests (#94) * dev-bug: fixed vscode freezes * test: refactor database init to support tests Co-authored-by: Hayden <hay-kot@pm.me> * Mealplan CRUD Tests (#95) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing Co-authored-by: Hayden <hay-kot@pm.me> * Fix typos (#96) * Settings, Themes and Migration Route Tests (#100) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing Co-authored-by: Hayden <hay-kot@pm.me> * Refactor + New Docker File (#105) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding Co-authored-by: Hayden <hay-kot@pm.me> * Meal planner improvements (#107) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding * mealplan redesign Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#108) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#113) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript * UI updates + name validation * docs: changelog + sp * fixed route links * changelog Co-authored-by: Hayden <hay-kot@pm.me> * fixed menu links * fixed poetry install on docker.dev build * Migration redesign (#119) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes Co-authored-by: Hayden <hay-kot@pm.me> * Fix link to dev-notes.md (#110) * translation: add swedish (#128) * language: da is Danish * translations: add swedish * scraper: unescape html in instructions (#129) Some urls erroneously deliver escaped html their instructions, sometimes they are even escaped on multiple levels like here: https://www.ica.se/recept/kladdig-kladdkaka-722982/ ``` >>> normalize_instruction("S&auml;tt ugnen p&aring; 200&deg;C.") 'Sätt ugnen på 200°C.' ``` * v0.2.0 Updates (#130) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes * test todos * bug/added docker volume * chowdow test data * partial image recipe image testing * added card section card * settings form * homepage cetegory ui * frontend category placeholder * fixed broken scheduler * remove old files * removed temp test Co-authored-by: Hayden <hay-kot@pm.me> * Fix missing translations key (#133) * translation: add simplified & traditional chinese * Fix missing translations * fix chinese translations * v0.2.0 Release Candidate (#141) * Fix link to Docker Hub Found an extra s. DESTROYED it. * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * notes hot fix * bug: mongo updates fail #99 * Fix error message (#101) * gh funding * Create Issue Templates (#125) * Create bug_report.md * Create config.yml Included a link to feature requests. * Update config.yml Fixed link I had for testing to the actual link * Update bug_report.md fix capitalization * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * merge kentors changes * refactor/recipe routers * category/tag database relationship and endpoints * frontend category management * update branch todos * bug/normalize recipe steps html * remove console.log + refactor categories * fix categories database errors * refactor/ router endpoint * refactor/ remove old code * drag and drop ingredients * general cleanup * route refactoring * changelog * api refactoring + random cleanup * fixed backwards sort * Update mkdocs.yml (#137) Fix warning from Deploy Docs github action * fixed navigate on enter in search * refactor/create global css * added category scroll * cleanup todos * debug routes * docs/new gifs & general updates * cleanup * fix list test Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * fix build * fix duplicate editor * fixed docker mount problem * python 3.9 * added tasks for non-docker development * remove old scripts * dev updates * fixed no image upload option * get version from backend * final docs pass * .gitignore * feature/markdown support for description and steps * package-lock * rename production task * category import errors on import (#150) Co-authored-by: hay-kot <hay-kot@pm.me> * Feature/improved notifications (#152) * category import errors on import * Import Error Hotfix (#148) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog * Dropping Mongo From Dev Branch (#89) * Fix link to Docker Hub Found an extra s. DESTROYED it. * initial pass * second pass cleanup * backup card framework * backup card functionality * translation * upload button vile creation * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * dev bug: change data location to prevent reloads * api docs * api docs bug * workflow update Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * Add French Translation (#93) * New tests (#94) * dev-bug: fixed vscode freezes * test: refactor database init to support tests Co-authored-by: Hayden <hay-kot@pm.me> * Mealplan CRUD Tests (#95) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing Co-authored-by: Hayden <hay-kot@pm.me> * Fix typos (#96) * Settings, Themes and Migration Route Tests (#100) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing Co-authored-by: Hayden <hay-kot@pm.me> * Refactor + New Docker File (#105) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding Co-authored-by: Hayden <hay-kot@pm.me> * Meal planner improvements (#107) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding * mealplan redesign Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#108) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#113) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript * UI updates + name validation * docs: changelog + sp * fixed route links * changelog Co-authored-by: Hayden <hay-kot@pm.me> * fixed menu links * fixed poetry install on docker.dev build * Migration redesign (#119) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes Co-authored-by: Hayden <hay-kot@pm.me> * Fix link to dev-notes.md (#110) * translation: add swedish (#128) * language: da is Danish * translations: add swedish * scraper: unescape html in instructions (#129) Some urls erroneously deliver escaped html their instructions, sometimes they are even escaped on multiple levels like here: https://www.ica.se/recept/kladdig-kladdkaka-722982/ ``` >>> normalize_instruction("S&auml;tt ugnen p&aring; 200&deg;C.") 'Sätt ugnen på 200°C.' ``` * v0.2.0 Updates (#130) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes * test todos * bug/added docker volume * chowdow test data * partial image recipe image testing * added card section card * settings form * homepage cetegory ui * frontend category placeholder * fixed broken scheduler * remove old files * removed temp test Co-authored-by: Hayden <hay-kot@pm.me> * Fix missing translations key (#133) * translation: add simplified & traditional chinese * Fix missing translations * fix chinese translations * v0.2.0 Release Candidate (#141) * Fix link to Docker Hub Found an extra s. DESTROYED it. * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * notes hot fix * bug: mongo updates fail #99 * Fix error message (#101) * gh funding * Create Issue Templates (#125) * Create bug_report.md * Create config.yml Included a link to feature requests. * Update config.yml Fixed link I had for testing to the actual link * Update bug_report.md fix capitalization * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * merge kentors changes * refactor/recipe routers * category/tag database relationship and endpoints * frontend category management * update branch todos * bug/normalize recipe steps html * remove console.log + refactor categories * fix categories database errors * refactor/ router endpoint * refactor/ remove old code * drag and drop ingredients * general cleanup * route refactoring * changelog * api refactoring + random cleanup * fixed backwards sort * Update mkdocs.yml (#137) Fix warning from Deploy Docs github action * fixed navigate on enter in search * refactor/create global css * added category scroll * cleanup todos * debug routes * docs/new gifs & general updates * cleanup * fix list test Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * fix build * fix duplicate editor * fixed docker mount problem * python 3.9 * added tasks for non-docker development * remove old scripts * dev updates * fixed no image upload option * get version from backend * final docs pass * .gitignore * feature/markdown support for description and steps * package-lock * rename production task * category import errors on import Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <wengtad93@gmail.com> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * test notifications * unified notifications * docs/changelog Co-authored-by: hay-kot <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <wengtad93@gmail.com> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * Refactor/response models (#156) - First Pass * cleanup * split app/db versioning * async file response * refactor/recipe viewer + minor ui improvements * auto grow size * added async file responses * docs/changelog * "/" to open search bar * docs/changelog * change imports to use @/ for imports * cleanup * cleanup * db to session * theme + settings refactor * bug/image save fix * fixed failing tests * fix last json bug - #155 * fix settings import * fixed router link for site title Co-authored-by: hay-kot <hay-kot@pm.me> * version update * Refactor/response models (#161) * cleanup * split app/db versioning * async file response * refactor/recipe viewer + minor ui improvements * auto grow size * added async file responses * docs/changelog * "/" to open search bar * docs/changelog * change imports to use @/ for imports * cleanup * cleanup * db to session * theme + settings refactor * bug/image save fix * fixed failing tests * fix last json bug - #155 * fix settings import * fixed router link for site title * add version tag * v0.2.1 (#157) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog * Dropping Mongo From Dev Branch (#89) * Fix link to Docker Hub Found an extra s. DESTROYED it. * initial pass * second pass cleanup * backup card framework * backup card functionality * translation * upload button vile creation * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * dev bug: change data location to prevent reloads * api docs * api docs bug * workflow update Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * Add French Translation (#93) * New tests (#94) * dev-bug: fixed vscode freezes * test: refactor database init to support tests Co-authored-by: Hayden <hay-kot@pm.me> * Mealplan CRUD Tests (#95) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing Co-authored-by: Hayden <hay-kot@pm.me> * Fix typos (#96) * Settings, Themes and Migration Route Tests (#100) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing Co-authored-by: Hayden <hay-kot@pm.me> * Refactor + New Docker File (#105) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding Co-authored-by: Hayden <hay-kot@pm.me> * Meal planner improvements (#107) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding * mealplan redesign Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#108) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#113) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript * UI updates + name validation * docs: changelog + sp * fixed route links * changelog Co-authored-by: Hayden <hay-kot@pm.me> * fixed menu links * fixed poetry install on docker.dev build * Migration redesign (#119) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes Co-authored-by: Hayden <hay-kot@pm.me> * Fix link to dev-notes.md (#110) * translation: add swedish (#128) * language: da is Danish * translations: add swedish * scraper: unescape html in instructions (#129) Some urls erroneously deliver escaped html their instructions, sometimes they are even escaped on multiple levels like here: https://www.ica.se/recept/kladdig-kladdkaka-722982/ ``` >>> normalize_instruction("S&auml;tt ugnen p&aring; 200&deg;C.") 'Sätt ugnen på 200°C.' ``` * v0.2.0 Updates (#130) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes * test todos * bug/added docker volume * chowdow test data * partial image recipe image testing * added card section card * settings form * homepage cetegory ui * frontend category placeholder * fixed broken scheduler * remove old files * removed temp test Co-authored-by: Hayden <hay-kot@pm.me> * Fix missing translations key (#133) * translation: add simplified & traditional chinese * Fix missing translations * fix chinese translations * v0.2.0 Release Candidate (#141) * Fix link to Docker Hub Found an extra s. DESTROYED it. * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * notes hot fix * bug: mongo updates fail #99 * Fix error message (#101) * gh funding * Create Issue Templates (#125) * Create bug_report.md * Create config.yml Included a link to feature requests. * Update config.yml Fixed link I had for testing to the actual link * Update bug_report.md fix capitalization * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * merge kentors changes * refactor/recipe routers * category/tag database relationship and endpoints * frontend category management * update branch todos * bug/normalize recipe steps html * remove console.log + refactor categories * fix categories database errors * refactor/ router endpoint * refactor/ remove old code * drag and drop ingredients * general cleanup * route refactoring * changelog * api refactoring + random cleanup * fixed backwards sort * Update mkdocs.yml (#137) Fix warning from Deploy Docs github action * fixed navigate on enter in search * refactor/create global css * added category scroll * cleanup todos * debug routes * docs/new gifs & general updates * cleanup * fix list test Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * fix build * fix duplicate editor * fixed docker mount problem * python 3.9 * added tasks for non-docker development * remove old scripts * dev updates * fixed no image upload option * get version from backend * final docs pass * .gitignore * feature/markdown support for description and steps * package-lock * rename production task * category import errors on import (#150) Co-authored-by: hay-kot <hay-kot@pm.me> * Feature/improved notifications (#152) * category import errors on import * Import Error Hotfix (#148) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog * Dropping Mongo From Dev Branch (#89) * Fix link to Docker Hub Found an extra s. DESTROYED it. * initial pass * second pass cleanup * backup card framework * backup card functionality * translation * upload button vile creation * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * dev bug: change data location to prevent reloads * api docs * api docs bug * workflow update Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * Add French Translation (#93) * New tests (#94) * dev-bug: fixed vscode freezes * test: refactor database init to support tests Co-authored-by: Hayden <hay-kot@pm.me> * Mealplan CRUD Tests (#95) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing Co-authored-by: Hayden <hay-kot@pm.me> * Fix typos (#96) * Settings, Themes and Migration Route Tests (#100) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing Co-authored-by: Hayden <hay-kot@pm.me> * Refactor + New Docker File (#105) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding Co-authored-by: Hayden <hay-kot@pm.me> * Meal planner improvements (#107) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding * mealplan redesign Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#108) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#113) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript * UI updates + name validation * docs: changelog + sp * fixed route links * changelog Co-authored-by: Hayden <hay-kot@pm.me> * fixed menu links * fixed poetry install on docker.dev build * Migration redesign (#119) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes Co-authored-by: Hayden <hay-kot@pm.me> * Fix link to dev-notes.md (#110) * translation: add swedish (#128) * language: da is Danish * translations: add swedish * scraper: unescape html in instructions (#129) Some urls erroneously deliver escaped html their instructions, sometimes they are even escaped on multiple levels like here: https://www.ica.se/recept/kladdig-kladdkaka-722982/ ``` >>> normalize_instruction("S&auml;tt ugnen p&aring; 200&deg;C.") 'Sätt ugnen på 200°C.' ``` * v0.2.0 Updates (#130) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes * test todos * bug/added docker volume * chowdow test data * partial image recipe image testing * added card section card * settings form * homepage cetegory ui * frontend category placeholder * fixed broken scheduler * remove old files * removed temp test Co-authored-by: Hayden <hay-kot@pm.me> * Fix missing translations key (#133) * translation: add simplified & traditional chinese * Fix missing translations * fix chinese translations * v0.2.0 Release Candidate (#141) * Fix link to Docker Hub Found an extra s. DESTROYED it. * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * notes hot fix * bug: mongo updates fail #99 * Fix error message (#101) * gh funding * Create Issue Templates (#125) * Create bug_report.md * Create config.yml Included a link to feature requests. * Update config.yml Fixed link I had for testing to the actual link * Update bug_report.md fix capitalization * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * merge kentors changes * refactor/recipe routers * category/tag database relationship and endpoints * frontend category management * update branch todos * bug/normalize recipe steps html * remove console.log + refactor categories * fix categories database errors * refactor/ router endpoint * refactor/ remove old code * drag and drop ingredients * general cleanup * route refactoring * changelog * api refactoring + random cleanup * fixed backwards sort * Update mkdocs.yml (#137) Fix warning from Deploy Docs github action * fixed navigate on enter in search * refactor/create global css * added category scroll * cleanup todos * debug routes * docs/new gifs & general updates * cleanup * fix list test Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * fix build * fix duplicate editor * fixed docker mount problem * python 3.9 * added tasks for non-docker development * remove old scripts * dev updates * fixed no image upload option * get version from backend * final docs pass * .gitignore * feature/markdown support for description and steps * package-lock * rename production task * category import errors on import Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <wengtad93@gmail.com> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * test notifications * unified notifications * docs/changelog Co-authored-by: hay-kot <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <wengtad93@gmail.com> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * Refactor/response models (#156) - First Pass * cleanup * split app/db versioning * async file response * refactor/recipe viewer + minor ui improvements * auto grow size * added async file responses * docs/changelog * "/" to open search bar * docs/changelog * change imports to use @/ for imports * cleanup * cleanup * db to session * theme + settings refactor * bug/image save fix * fixed failing tests * fix last json bug - #155 * fix settings import * fixed router link for site title Co-authored-by: hay-kot <hay-kot@pm.me> Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <wengtad93@gmail.com> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * Revert "v0.2.1 (#157)" (#158) This reverts commita899f46464. * v0.2.1 (#159) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog * Dropping Mongo From Dev Branch (#89) * Fix link to Docker Hub Found an extra s. DESTROYED it. * initial pass * second pass cleanup * backup card framework * backup card functionality * translation * upload button vile creation * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * dev bug: change data location to prevent reloads * api docs * api docs bug * workflow update Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * Add French Translation (#93) * New tests (#94) * dev-bug: fixed vscode freezes * test: refactor database init to support tests Co-authored-by: Hayden <hay-kot@pm.me> * Mealplan CRUD Tests (#95) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing Co-authored-by: Hayden <hay-kot@pm.me> * Fix typos (#96) * Settings, Themes and Migration Route Tests (#100) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing Co-authored-by: Hayden <hay-kot@pm.me> * Refactor + New Docker File (#105) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding Co-authored-by: Hayden <hay-kot@pm.me> * Meal planner improvements (#107) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding * mealplan redesign Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#108) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#113) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript * UI updates + name validation * docs: changelog + sp * fixed route links * changelog Co-authored-by: Hayden <hay-kot@pm.me> * fixed menu links * fixed poetry install on docker.dev build * Migration redesign (#119) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes Co-authored-by: Hayden <hay-kot@pm.me> * Fix link to dev-notes.md (#110) * translation: add swedish (#128) * language: da is Danish * translations: add swedish * scraper: unescape html in instructions (#129) Some urls erroneously deliver escaped html their instructions, sometimes they are even escaped on multiple levels like here: https://www.ica.se/recept/kladdig-kladdkaka-722982/ ``` >>> normalize_instruction("S&auml;tt ugnen p&aring; 200&deg;C.") 'Sätt ugnen på 200°C.' ``` * v0.2.0 Updates (#130) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes * test todos * bug/added docker volume * chowdow test data * partial image recipe image testing * added card section card * settings form * homepage cetegory ui * frontend category placeholder * fixed broken scheduler * remove old files * removed temp test Co-authored-by: Hayden <hay-kot@pm.me> * Fix missing translations key (#133) * translation: add simplified & traditional chinese * Fix missing translations * fix chinese translations * v0.2.0 Release Candidate (#141) * Fix link to Docker Hub Found an extra s. DESTROYED it. * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * notes hot fix * bug: mongo updates fail #99 * Fix error message (#101) * gh funding * Create Issue Templates (#125) * Create bug_report.md * Create config.yml Included a link to feature requests. * Update config.yml Fixed link I had for testing to the actual link * Update bug_report.md fix capitalization * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * merge kentors changes * refactor/recipe routers * category/tag database relationship and endpoints * frontend category management * update branch todos * bug/normalize recipe steps html * remove console.log + refactor categories * fix categories database errors * refactor/ router endpoint * refactor/ remove old code * drag and drop ingredients * general cleanup * route refactoring * changelog * api refactoring + random cleanup * fixed backwards sort * Update mkdocs.yml (#137) Fix warning from Deploy Docs github action * fixed navigate on enter in search * refactor/create global css * added category scroll * cleanup todos * debug routes * docs/new gifs & general updates * cleanup * fix list test Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * fix build * fix duplicate editor * fixed docker mount problem * python 3.9 * added tasks for non-docker development * remove old scripts * dev updates * fixed no image upload option * get version from backend * final docs pass * .gitignore * feature/markdown support for description and steps * package-lock * rename production task * category import errors on import (#150) Co-authored-by: hay-kot <hay-kot@pm.me> * Feature/improved notifications (#152) * category import errors on import * Import Error Hotfix (#148) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog * Dropping Mongo From Dev Branch (#89) * Fix link to Docker Hub Found an extra s. DESTROYED it. * initial pass * second pass cleanup * backup card framework * backup card functionality * translation * upload button vile creation * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * dev bug: change data location to prevent reloads * api docs * api docs bug * workflow update Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * Add French Translation (#93) * New tests (#94) * dev-bug: fixed vscode freezes * test: refactor database init to support tests Co-authored-by: Hayden <hay-kot@pm.me> * Mealplan CRUD Tests (#95) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing Co-authored-by: Hayden <hay-kot@pm.me> * Fix typos (#96) * Settings, Themes and Migration Route Tests (#100) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing Co-authored-by: Hayden <hay-kot@pm.me> * Refactor + New Docker File (#105) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding Co-authored-by: Hayden <hay-kot@pm.me> * Meal planner improvements (#107) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding * mealplan redesign Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#108) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#113) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript * UI updates + name validation * docs: changelog + sp * fixed route links * changelog Co-authored-by: Hayden <hay-kot@pm.me> * fixed menu links * fixed poetry install on docker.dev build * Migration redesign (#119) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes Co-authored-by: Hayden <hay-kot@pm.me> * Fix link to dev-notes.md (#110) * translation: add swedish (#128) * language: da is Danish * translations: add swedish * scraper: unescape html in instructions (#129) Some urls erroneously deliver escaped html their instructions, sometimes they are even escaped on multiple levels like here: https://www.ica.se/recept/kladdig-kladdkaka-722982/ ``` >>> normalize_instruction("S&auml;tt ugnen p&aring; 200&deg;C.") 'Sätt ugnen på 200°C.' ``` * v0.2.0 Updates (#130) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes * test todos * bug/added docker volume * chowdow test data * partial image recipe image testing * added card section card * settings form * homepage cetegory ui * frontend category placeholder * fixed broken scheduler * remove old files * removed temp test Co-authored-by: Hayden <hay-kot@pm.me> * Fix missing translations key (#133) * translation: add simplified & traditional chinese * Fix missing translations * fix chinese translations * v0.2.0 Release Candidate (#141) * Fix link to Docker Hub Found an extra s. DESTROYED it. * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * notes hot fix * bug: mongo updates fail #99 * Fix error message (#101) * gh funding * Create Issue Templates (#125) * Create bug_report.md * Create config.yml Included a link to feature requests. * Update config.yml Fixed link I had for testing to the actual link * Update bug_report.md fix capitalization * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * merge kentors changes * refactor/recipe routers * category/tag database relationship and endpoints * frontend category management * update branch todos * bug/normalize recipe steps html * remove console.log + refactor categories * fix categories database errors * refactor/ router endpoint * refactor/ remove old code * drag and drop ingredients * general cleanup * route refactoring * changelog * api refactoring + random cleanup * fixed backwards sort * Update mkdocs.yml (#137) Fix warning from Deploy Docs github action * fixed navigate on enter in search * refactor/create global css * added category scroll * cleanup todos * debug routes * docs/new gifs & general updates * cleanup * fix list test Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * fix build * fix duplicate editor * fixed docker mount problem * python 3.9 * added tasks for non-docker development * remove old scripts * dev updates * fixed no image upload option * get version from backend * final docs pass * .gitignore * feature/markdown support for description and steps * package-lock * rename production task * category import errors on import Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <wengtad93@gmail.com> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * test notifications * unified notifications * docs/changelog Co-authored-by: hay-kot <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <wengtad93@gmail.com> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * Refactor/response models (#156) - First Pass * cleanup * split app/db versioning * async file response * refactor/recipe viewer + minor ui improvements * auto grow size * added async file responses * docs/changelog * "/" to open search bar * docs/changelog * change imports to use @/ for imports * cleanup * cleanup * db to session * theme + settings refactor * bug/image save fix * fixed failing tests * fix last json bug - #155 * fix settings import * fixed router link for site title Co-authored-by: hay-kot <hay-kot@pm.me> * version update Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <… * Consider any .json file from Nextcloud (#164) * Allow empty instructions (#165) * fix scrape recipe error (#166) - fixes #138 - fixes #145 * Add polish translation (#154) * add polish translation * add missing allLangs value for polish translation in language.js * Create de.json (#167) Add German Translation * Refactor/response models (#169) * cleanup * split app/db versioning * async file response * refactor/recipe viewer + minor ui improvements * auto grow size * added async file responses * docs/changelog * "/" to open search bar * docs/changelog * change imports to use @/ for imports * cleanup * cleanup * db to session * theme + settings refactor * bug/image save fix * fixed failing tests * fix last json bug - #155 * fix settings import * fixed router link for site title * add version tag * v0.2.1 (#157) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog * Dropping Mongo From Dev Branch (#89) * Fix link to Docker Hub Found an extra s. DESTROYED it. * initial pass * second pass cleanup * backup card framework * backup card functionality * translation * upload button vile creation * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * dev bug: change data location to prevent reloads * api docs * api docs bug * workflow update Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * Add French Translation (#93) * New tests (#94) * dev-bug: fixed vscode freezes * test: refactor database init to support tests Co-authored-by: Hayden <hay-kot@pm.me> * Mealplan CRUD Tests (#95) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing Co-authored-by: Hayden <hay-kot@pm.me> * Fix typos (#96) * Settings, Themes and Migration Route Tests (#100) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing Co-authored-by: Hayden <hay-kot@pm.me> * Refactor + New Docker File (#105) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding Co-authored-by: Hayden <hay-kot@pm.me> * Meal planner improvements (#107) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding * mealplan redesign Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#108) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#113) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript * UI updates + name validation * docs: changelog + sp * fixed route links * changelog Co-authored-by: Hayden <hay-kot@pm.me> * fixed menu links * fixed poetry install on docker.dev build * Migration redesign (#119) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes Co-authored-by: Hayden <hay-kot@pm.me> * Fix link to dev-notes.md (#110) * translation: add swedish (#128) * language: da is Danish * translations: add swedish * scraper: unescape html in instructions (#129) Some urls erroneously deliver escaped html their instructions, sometimes they are even escaped on multiple levels like here: https://www.ica.se/recept/kladdig-kladdkaka-722982/ ``` >>> normalize_instruction("S&auml;tt ugnen p&aring; 200&deg;C.") 'Sätt ugnen på 200°C.' ``` * v0.2.0 Updates (#130) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes * test todos * bug/added docker volume * chowdow test data * partial image recipe image testing * added card section card * settings form * homepage cetegory ui * frontend category placeholder * fixed broken scheduler * remove old files * removed temp test Co-authored-by: Hayden <hay-kot@pm.me> * Fix missing translations key (#133) * translation: add simplified & traditional chinese * Fix missing translations * fix chinese translations * v0.2.0 Release Candidate (#141) * Fix link to Docker Hub Found an extra s. DESTROYED it. * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * notes hot fix * bug: mongo updates fail #99 * Fix error message (#101) * gh funding * Create Issue Templates (#125) * Create bug_report.md * Create config.yml Included a link to feature requests. * Update config.yml Fixed link I had for testing to the actual link * Update bug_report.md fix capitalization * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * merge kentors changes * refactor/recipe routers * category/tag database relationship and endpoints * frontend category management * update branch todos * bug/normalize recipe steps html * remove console.log + refactor categories * fix categories database errors * refactor/ router endpoint * refactor/ remove old code * drag and drop ingredients * general cleanup * route refactoring * changelog * api refactoring + random cleanup * fixed backwards sort * Update mkdocs.yml (#137) Fix warning from Deploy Docs github action * fixed navigate on enter in search * refactor/create global css * added category scroll * cleanup todos * debug routes * docs/new gifs & general updates * cleanup * fix list test Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * fix build * fix duplicate editor * fixed docker mount problem * python 3.9 * added tasks for non-docker development * remove old scripts * dev updates * fixed no image upload option * get version from backend * final docs pass * .gitignore * feature/markdown support for description and steps * package-lock * rename production task * category import errors on import (#150) Co-authored-by: hay-kot <hay-kot@pm.me> * Feature/improved notifications (#152) * category import errors on import * Import Error Hotfix (#148) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog * Dropping Mongo From Dev Branch (#89) * Fix link to Docker Hub Found an extra s. DESTROYED it. * initial pass * second pass cleanup * backup card framework * backup card functionality * translation * upload button vile creation * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * dev bug: change data location to prevent reloads * api docs * api docs bug * workflow update Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * Add French Translation (#93) * New tests (#94) * dev-bug: fixed vscode freezes * test: refactor database init to support tests Co-authored-by: Hayden <hay-kot@pm.me> * Mealplan CRUD Tests (#95) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing Co-authored-by: Hayden <hay-kot@pm.me> * Fix typos (#96) * Settings, Themes and Migration Route Tests (#100) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing Co-authored-by: Hayden <hay-kot@pm.me> * Refactor + New Docker File (#105) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding Co-authored-by: Hayden <hay-kot@pm.me> * Meal planner improvements (#107) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding * mealplan redesign Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#108) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#113) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript * UI updates + name validation * docs: changelog + sp * fixed route links * changelog Co-authored-by: Hayden <hay-kot@pm.me> * fixed menu links * fixed poetry install on docker.dev build * Migration redesign (#119) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes Co-authored-by: Hayden <hay-kot@pm.me> * Fix link to dev-notes.md (#110) * translation: add swedish (#128) * language: da is Danish * translations: add swedish * scraper: unescape html in instructions (#129) Some urls erroneously deliver escaped html their instructions, sometimes they are even escaped on multiple levels like here: https://www.ica.se/recept/kladdig-kladdkaka-722982/ ``` >>> normalize_instruction("S&auml;tt ugnen p&aring; 200&deg;C.") 'Sätt ugnen på 200°C.' ``` * v0.2.0 Updates (#130) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes * test todos * bug/added docker volume * chowdow test data * partial image recipe image testing * added card section card * settings form * homepage cetegory ui * frontend category placeholder * fixed broken scheduler * remove old files * removed temp test Co-authored-by: Hayden <hay-kot@pm.me> * Fix missing translations key (#133) * translation: add simplified & traditional chinese * Fix missing translations * fix chinese translations * v0.2.0 Release Candidate (#141) * Fix link to Docker Hub Found an extra s. DESTROYED it. * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * notes hot fix * bug: mongo updates fail #99 * Fix error message (#101) * gh funding * Create Issue Templates (#125) * Create bug_report.md * Create config.yml Included a link to feature requests. * Update config.yml Fixed link I had for testing to the actual link * Update bug_report.md fix capitalization * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * merge kentors changes * refactor/recipe routers * category/tag database relationship and endpoints * frontend category management * update branch todos * bug/normalize recipe steps html * remove console.log + refactor categories * fix categories database errors * refactor/ router endpoint * refactor/ remove old code * drag and drop ingredients * general cleanup * route refactoring * changelog * api refactoring + random cleanup * fixed backwards sort * Update mkdocs.yml (#137) Fix warning from Deploy Docs github action * fixed navigate on enter in search * refactor/create global css * added category scroll * cleanup todos * debug routes * docs/new gifs & general updates * cleanup * fix list test Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * fix build * fix duplicate editor * fixed docker mount problem * python 3.9 * added tasks for non-docker development * remove old scripts * dev updates * fixed no image upload option * get version from backend * final docs pass * .gitignore * feature/markdown support for description and steps * package-lock * rename production task * category import errors on import Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <wengtad93@gmail.com> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * test notifications * unified notifications * docs/changelog Co-authored-by: hay-kot <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <wengtad93@gmail.com> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * Refactor/response models (#156) - First Pass * cleanup * split app/db versioning * async file response * refactor/recipe viewer + minor ui improvements * auto grow size * added async file responses * docs/changelog * "/" to open search bar * docs/changelog * change imports to use @/ for imports * cleanup * cleanup * db to session * theme + settings refactor * bug/image save fix * fixed failing tests * fix last json bug - #155 * fix settings import * fixed router link for site title Co-authored-by: hay-kot <hay-kot@pm.me> Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <wengtad93@gmail.com> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * Revert "v0.2.1 (#157)" (#158) This reverts commita899f46464. * v0.2.1 (#159) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog * Dropping Mongo From Dev Branch (#89) * Fix link to Docker Hub Found an extra s. DESTROYED it. * initial pass * second pass cleanup * backup card framework * backup card functionality * translation * upload button vile creation * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * dev bug: change data location to prevent reloads * api docs * api docs bug * workflow update Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * Add French Translation (#93) * New tests (#94) * dev-bug: fixed vscode freezes * test: refactor database init to support tests Co-authored-by: Hayden <hay-kot@pm.me> * Mealplan CRUD Tests (#95) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing Co-authored-by: Hayden <hay-kot@pm.me> * Fix typos (#96) * Settings, Themes and Migration Route Tests (#100) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing Co-authored-by: Hayden <hay-kot@pm.me> * Refactor + New Docker File (#105) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding Co-authored-by: Hayden <hay-kot@pm.me> * Meal planner improvements (#107) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding * mealplan redesign Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#108) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#113) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript * UI updates + name validation * docs: changelog + sp * fixed route links * changelog Co-authored-by: Hayden <hay-kot@pm.me> * fixed menu links * fixed poetry install on docker.dev build * Migration redesign (#119) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes Co-authored-by: Hayden <hay-kot@pm.me> * Fix link to dev-notes.md (#110) * translation: add swedish (#128) * language: da is Danish * translations: add swedish * scraper: unescape html in instructions (#129) Some urls erroneously deliver escaped html their instructions, sometimes they are even escaped on multiple levels like here: https://www.ica.se/recept/kladdig-kladdkaka-722982/ ``` >>> normalize_instruction("S&auml;tt ugnen p&aring; 200&deg;C.") 'Sätt ugnen på 200°C.' ``` * v0.2.0 Updates (#130) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes * test todos * bug/added docker volume * chowdow test data * partial image recipe image testing * added card section card * settings form * homepage cetegory ui * frontend category placeholder * fixed broken scheduler * remove old files * removed temp test Co-authored-by: Hayden <hay-kot@pm.me> * Fix missing translations key (#133) * translation: add simplified & traditional chinese * Fix missing translations * fix chinese translations * v0.2.0 Release Candidate (#141) * Fix link to Docker Hub Found an extra s. DESTROYED it. * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * notes hot fix * bug: mongo updates fail #99 * Fix error message (#101) * gh funding * Create Issue Templates (#125) * Create bug_report.md * Create config.yml Included a link to feature requests. * Update config.yml Fixed link I had for testing to the actual link * Update bug_report.md fix capitalization * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * merge kentors changes * refactor/recipe routers * category/tag database relationship and endpoints * frontend category management * update branch todos * bug/normalize recipe steps html * remove console.log + refactor categories * fix categories database errors * refactor/ router endpoint * refactor/ remove old code * drag and drop ingredients * general cleanup * route refactoring * changelog * api refactoring + random cleanup * fixed backwards sort * Update mkdocs.yml (#137) Fix warning from Deploy Docs github action * fixed navigate on enter in search * refactor/create global css * added category scroll * cleanup todos * debug routes * docs/new gifs & general updates * cleanup * fix list test Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * fix build * fix duplicate editor * fixed docker mount problem * python 3.9 * added tasks for non-docker development * remove old scripts * dev updates * fixed no image upload option * get version from backend * final docs pass * .gitignore * feature/markdown support for description and steps * package-lock * rename production task * category import errors on import (#150) Co-authored-by: hay-kot <hay-kot@pm.me> * Feature/improved notifications (#152) * category import errors on import * Import Error Hotfix (#148) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog * Dropping Mongo From Dev Branch (#89) * Fix link to Docker Hub Found an extra s. DESTROYED it. * initial pass * second pass cleanup * backup card framework * backup card functionality * translation * upload button vile creation * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * dev bug: change data location to prevent reloads * api docs * api docs bug * workflow update Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * Add French Translation (#93) * New tests (#94) * dev-bug: fixed vscode freezes * test: refactor database init to support tests Co-authored-by: Hayden <hay-kot@pm.me> * Mealplan CRUD Tests (#95) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing Co-authored-by: Hayden <hay-kot@pm.me> * Fix typos (#96) * Settings, Themes and Migration Route Tests (#100) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing Co-authored-by: Hayden <hay-kot@pm.me> * Refactor + New Docker File (#105) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding Co-authored-by: Hayden <hay-kot@pm.me> * Meal planner improvements (#107) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding * mealplan redesign Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#108) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#113) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript * UI updates + name validation * docs: changelog + sp * fixed route links * changelog Co-authored-by: Hayden <hay-kot@pm.me> * fixed menu links * fixed poetry install on docker.dev build * Migration redesign (#119) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes Co-authored-by: Hayden <hay-kot@pm.me> * Fix link to dev-notes.md (#110) * translation: add swedish (#128) * language: da is Danish * translations: add swedish * scraper: unescape html in instructions (#129) Some urls erroneously deliver escaped html their instructions, sometimes they are even escaped on multiple levels like here: https://www.ica.se/recept/kladdig-kladdkaka-722982/ ``` >>> normalize_instruction("S&auml;tt ugnen p&aring; 200&deg;C.") 'Sätt ugnen på 200°C.' ``` * v0.2.0 Updates (#130) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes * test todos * bug/added docker volume * chowdow test data * partial image recipe image testing * added card section card * settings form * homepage cetegory ui * frontend category placeholder * fixed broken scheduler * remove old files * removed temp test Co-authored-by: Hayden <hay-kot@pm.me> * Fix missing translations key (#133) * translation: add simplified & traditional chinese * Fix missing translations * fix chinese translations * v0.2.0 Release Candidate (#141) * Fix link to Docker Hub Found an extra s. DESTROYED it. * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * notes hot fix * bug: mongo updates fail #99 * Fix error message (#101) * gh funding * Create Issue Templates (#125) * Create bug_report.md * Create config.yml Included a link to feature requests. * Update config.yml Fixed link I had for testing to the actual link * Update bug_report.md fix capitalization * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * merge kentors changes * refactor/recipe routers * category/tag database relationship and endpoints * frontend category management * update branch todos * bug/normalize recipe steps html * remove console.log + refactor categories * fix categories database errors * refactor/ router endpoint * refactor/ remove old code * drag and drop ingredients * general cleanup * route refactoring * changelog * api refactoring + random cleanup * fixed backwards sort * Update mkdocs.yml (#137) Fix warning from Deploy Docs github action * fixed navigate on enter in search * refactor/create global css * added category scroll * cleanup todos * debug routes * docs/new gifs & general updates * cleanup * fix list test Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * fix build * fix duplicate editor * fixed docker mount problem * python 3.9 * added tasks for non-docker development * remove old scripts * dev updates * fixed no image upload option * get version from backend * final docs pass * .gitignore * feature/markdown support for description and steps * package-lock * rename production task * category import errors on import Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <wengtad93@gmail.com> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * test notifications * unified notifications * docs/changelog Co-authored-by: hay-kot <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <wengtad93@gmail.com> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * Refactor/response models (#156) - First Pass * cleanup * split app/db versioning * async file response * refactor/recipe viewer + minor ui improvements * auto grow size * added async file responses * docs/changelog * "/" to open search bar * docs/changelog * change imports to use @/ for imports * cleanup * cleanup * db to session * theme + settings refactor * bug/image save fix * fixed failing tests * fix last json bug - #155 * fix settings import * fixed router link for site title Co-authored-by: hay-kot <hay-kot@pm.me> * version update Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <… * Enhance mobile view (#171) * Fixed navbar on mobile * Feature/shopping list (#172) * API Endpoint * shopping list added to the UI * fixed category sidebar on mobile Co-authored-by: hay-kot <hay-kot@pm.me> * Feature/shopping list (#173) * API Endpoint * shopping list added to the UI * fixed category sidebar on mobile * fix category sidebar hidden all the time * adjust mobile view on times * remove console.logs * actually remove console.logs * Fixed varying card height on smaller screens * change style of meal planner categories Co-authored-by: hay-kot <hay-kot@pm.me> * refactor/scrapper (#175) * API Endpoint * shopping list added to the UI * fixed category sidebar on mobile * fix category sidebar hidden all the time * adjust mobile view on times * remove console.logs * actually remove console.logs * Fixed varying card height on smaller screens * change style of meal planner categories * bug/fixed open search on '/' when on input * Improved import summary dialog * url validation * refactor/split reciper scraper into seperate fiels Co-authored-by: hay-kot <hay-kot@pm.me> * auto focus on add from url * docs/update v0.3.0 * remove print * docs/v0.3.0 * Localize homepage settings + FR translation (#180) * localize homepage settings vue * French localization for homepage settings * localize meal planner settings * Feature/authentication (#185) * 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 Co-authored-by: hay-kot <hay-kot@pm.me> * Fixes #183 (#187) * #183 added backend validation for mealplanning timespan * Fixes #183 Disabling save button when dateDif is negative, replacing non-functional invisible button(?) with proper spacing Co-authored-by: Bernhard Großer <30469627+boerniee@users.noreply.github.com> Co-authored-by: Andreas Waschinski <an.andreas@posteo.de> * Localized meal date (#182) * first step towards localized meal date * Refactor datetimeFormats to dedicated locale files * localized date in 'Dinner this week' * Feature/authentication (#190) * 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 Co-authored-by: hay-kot <hay-kot@pm.me> * Feature/authentication (#195) * 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 Co-authored-by: hay-kot <hay-kot@pm.me> * Initialize language settings earlier (#199) * Some titles were missing i18n (#197) Also slightly improved the german translation file. Co-authored-by: Andreas Waschinski <an.andreas@posteo.de> * Update fr.json (#204) Reference #124 French Added new strings + Updated some * Feature/authentication (#206) * 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 Co-authored-by: hay-kot <hay-kot@pm.me> * Feature/authentication (#207) * 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 Co-authored-by: hay-kot <hay-kot@pm.me> * Feature/authentication (#209) * 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 Co-authored-by: hay-kot <hay-kot@pm.me> * Feature/authentication (#210) * basic crud NOT SECURE * refactor/database init on startup * added scratch.py * tests/user CRUD routes * password hashing * change app_config location * bump python version * formatting * login ui starter * change import from url design * move components * remove old snackbar * refactor/Componenet folder structure rework * refactor/remove old code * refactor/rename componenets/js files * remove console.logs * refactor/ models to schema and sql to models * new header styling for imports * token request * fix url scrapper * refactor/rename schema files * split routes file * redesigned admin page * enable relative imports for vue components * refactor/switch to pages view * add CamelCase package * majors settings rework * user management second pass * super user CRUD * refactor/consistent models names * refactor/consistent model names * password reset * store refactor * dependency update * abstract button props * profile page refactor * basic password validation * login form refactor/split v-container * remo unused code * hide editor buttons when not logged in * mkdocs dev dependency * v0.4.0 docs update * profile image upload * additional token routes * Smaller recipe cards for smaller viewports * fix admin sidebar * add users * change to outlined * theme card starter * code cleanup * signups * signup pages * fix #194 * fix #193 * clarify mealie_port * fix #184 * fixes #178 * fix blank card error on meal-plan creator * admin signup * formatting * improved search bar * improved search bar * refresh token on page refresh * allow mealplan with no categories * fix card layout * remove cdn dependencies * start on groups * Fixes #196 * recipe databse refactor * changelog draft * database refactoring * refactor recipe schema/model * site settings refactor * continued model refactor * merge docs changes from master * site-settings work * cleanup + tag models * notes * typo * user table * sign up data validation * package updates * group store init * Fix home page settings * group admin init * group dashboard init * update deps * formatting * bug / added libffi-dev * pages refactor * fix mealplan * docs update * multi group supporot for job scheduler * formatting * formatting * home-page redesign * set background for docs darkmode * code cleanup * docs refactor * v0.4.0 image * mkdocs port change * formatting * Fix Meal-Plan Today * fix webhook bug * fix meal plan this week * export users Co-authored-by: hay-kot <hay-kot@pm.me> * More localization (#208) * remove unused strings * update i18n-ally settings * localized date picker * Translate CardSection * SiteMenu translatable * Translate TheUserTable * Use embedded Vuetify locales * localize TheUserTable * Localize group and sign up settings * Slightly enlarge group tile width * Localize admin side bar + user nav bar * Missing strings in Login form * Fix success message upon photo upload * Localize profile page * Missing translations in settings * 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> * Fix Docker/Dev ENV * 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 * uncomment docker-compose * reload in dev env Co-authored-by: hay-kot <hay-kot@pm.me> * Import/Export Overhall * 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 * uncomment docker-compose * reload in dev env * move developer data * fix upload issue * run init_db before startup * import groups and users * fix themes * fix theme * potentially fixes #216 * unlink test db * potentially fix #217 * localization * fix import errors on no group * fix hacky lxml error * fix import error Co-authored-by: hay-kot <hay-kot@pm.me> * Drop FastAPI-Login, add JWT management * 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 * uncomment docker-compose * reload in dev env * move developer data * fix upload issue * run init_db before startup * import groups and users * fix themes * fix theme * potentially fixes #216 * unlink test db * potentially fix #217 * localization * fix import errors on no group * fix hacky lxml error * fix import error * more import errors * test failing tests * fix/test that never really passed Co-authored-by: hay-kot <hay-kot@pm.me> * Feature/authentication (#220) * 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 * uncomment docker-compose * reload in dev env * move developer data * fix upload issue * run init_db before startup * import groups and users * fix themes * fix theme * potentially fixes #216 * unlink test db * potentially fix #217 * localization * fix import errors on no group * fix hacky lxml error * fix import error * more import errors * test failing tests * fix/test that never really passed * pin lxml version Co-authored-by: hay-kot <hay-kot@pm.me> * Feature/submit on enter key (#224) * general cleanup * submit on enter * fix signup form * fix duplicate slugs when testing Co-authored-by: hay-kot <hay-kot@pm.me> * Feature/site settings (#225) * general cleanup * submit on enter * fix signup form * fix duplicate slugs when testing * custom pages starter * api start * functional * docs * fix page reload on submit * set initial selection for category button * Site Pages Import/Export * documentation updates * generate .secret file for jwt hashing * properly align default passwords * set default password globally * set group for signups Co-authored-by: hay-kot <hay-kot@pm.me> * feature/secure-routes (#226) * secure routes * add token to test routes * fix duplicate data Co-authored-by: hay-kot <hay-kot@pm.me> * feature/password-reset (#227) * add reset default password * add warning * add version tag * bumb version * remove old footer Co-authored-by: hay-kot <hay-kot@pm.me> * fixes typos and spelling mistakes in de locales & translates new locales, #124 (#231) * chore: fixes typos and spelling mistakes in de locales, translates new locales, #124 * fix: uses personal form as in other locales instead of formal form Co-authored-by: Jonas Schubert <jonas.schubert.1990@web.de> * tests/suite-overhall - 83% Coverage * generate API docs with make file * documentation * code-gen scripts * type() to isinstance() * code-gen * fix flake8 problems * test refactor first pass * init config * added help, format, clean and lint * + flake8 developer dep * update docs * proper api imports * jsconfig * group tests * refactor settings to class for testing * fix env errors * change tool -> tools * code cleanup * sort imports * add tools test * lint Co-authored-by: hay-kot <hay-kot@pm.me> * General Frontend Bug Fixes for v0.4.0 RC (#233) * comment * add frontend-build command * address #211 * fix margins * fix import bug * await user updates * fix meal-plan filter * meal-plan search redesign * improve mobile search * fix sidebar update * fix category auto-completes * draft new pages * fix tag auto completes * refactor export const * dispatch evens for CRUD operations * recipe loaders screen * create category dialog Co-authored-by: hay-kot <hay-kot@pm.me> * New docs for v0.4.0 (#234) * spacing * fix parser bug * update for v0.4.0 * demo link * remove gifs * add organize diagram * demo code * remove large gifs * v0.4.0 changelog Co-authored-by: hay-kot <hay-kot@pm.me> * add test.db * typo Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <wengtad93@gmail.com> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> Co-authored-by: retmas-gh <58191209+retmas-gh@users.noreply.github.com> Co-authored-by: mindesbunister <32161838+mindesbunister@users.noreply.github.com> Co-authored-by: Raghnarok05 <77641728+Raghnarok05@users.noreply.github.com> Co-authored-by: Andreas Waschinski <an.andreas+github@posteo.de> Co-authored-by: Bernhard Großer <30469627+boerniee@users.noreply.github.com> Co-authored-by: Andreas Waschinski <an.andreas@posteo.de> Co-authored-by: JonasSchubert <jonas.schubert.projects@web.de> Co-authored-by: Jonas Schubert <jonas.schubert.1990@web.de>
This commit is contained in:
@@ -1,53 +1,43 @@
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi.logger import logger
|
||||
|
||||
# import utils.startup as startup
|
||||
from app_config import APP_VERSION, PORT, PRODUCTION, docs_url, redoc_url
|
||||
from routes import (
|
||||
backup_routes,
|
||||
debug_routes,
|
||||
meal_routes,
|
||||
migration_routes,
|
||||
setting_routes,
|
||||
theme_routes,
|
||||
)
|
||||
from routes.recipe import (
|
||||
all_recipe_routes,
|
||||
category_routes,
|
||||
recipe_crud_routes,
|
||||
tag_routes,
|
||||
)
|
||||
from services.settings_services import default_settings_init
|
||||
from utils.logger import logger
|
||||
from mealie.core.config import APP_VERSION, settings
|
||||
from mealie.routes import backup_routes, debug_routes, migration_routes, theme_routes
|
||||
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 mealie.routes.site_settings import all_settings
|
||||
from mealie.routes.users import users
|
||||
|
||||
app = FastAPI(
|
||||
title="Mealie",
|
||||
description="A place for all your recipes",
|
||||
version=APP_VERSION,
|
||||
docs_url=docs_url,
|
||||
redoc_url=redoc_url,
|
||||
docs_url=settings.DOCS_URL,
|
||||
redoc_url=settings.REDOC_URL,
|
||||
)
|
||||
|
||||
|
||||
def start_scheduler():
|
||||
import services.scheduler.scheduled_jobs
|
||||
|
||||
|
||||
def init_settings():
|
||||
default_settings_init()
|
||||
import services.theme_services
|
||||
import mealie.services.scheduler.scheduled_jobs # noqa: F401
|
||||
|
||||
|
||||
def api_routers():
|
||||
# Authentication
|
||||
app.include_router(users.router)
|
||||
app.include_router(groups.router)
|
||||
# Recipes
|
||||
app.include_router(all_recipe_routes.router)
|
||||
app.include_router(category_routes.router)
|
||||
app.include_router(tag_routes.router)
|
||||
app.include_router(recipe_crud_routes.router)
|
||||
|
||||
# Meal Routes
|
||||
app.include_router(meal_routes.router)
|
||||
app.include_router(mealplans.router)
|
||||
# Settings Routes
|
||||
app.include_router(setting_routes.router)
|
||||
app.include_router(all_settings.router)
|
||||
app.include_router(theme_routes.router)
|
||||
# Backups/Imports Routes
|
||||
app.include_router(backup_routes.router)
|
||||
@@ -56,21 +46,25 @@ def api_routers():
|
||||
app.include_router(debug_routes.router)
|
||||
|
||||
|
||||
|
||||
api_routers()
|
||||
start_scheduler()
|
||||
init_settings()
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("-----SYSTEM STARTUP-----")
|
||||
|
||||
def main():
|
||||
|
||||
uvicorn.run(
|
||||
"app:app",
|
||||
host="0.0.0.0",
|
||||
port=PORT,
|
||||
port=settings.API_PORT,
|
||||
reload=True,
|
||||
reload_dirs=["mealie"],
|
||||
debug=True,
|
||||
log_level="info",
|
||||
workers=1,
|
||||
forwarded_allow_ips="*",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("-----SYSTEM STARTUP-----")
|
||||
main()
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import dotenv
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
|
||||
|
||||
def ensure_dirs():
|
||||
for dir in REQUIRED_DIRS:
|
||||
dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
# Register ENV
|
||||
ENV = CWD.joinpath(".env")
|
||||
dotenv.load_dotenv(ENV)
|
||||
|
||||
# General
|
||||
APP_VERSION = "v0.3.0"
|
||||
DB_VERSION = "v0.2.1"
|
||||
PRODUCTION = os.environ.get("ENV")
|
||||
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
|
||||
|
||||
# Helpful Globals
|
||||
DATA_DIR = CWD.parent.joinpath("app_data")
|
||||
if PRODUCTION:
|
||||
DATA_DIR = Path("/app/data")
|
||||
|
||||
WEB_PATH = CWD.joinpath("dist")
|
||||
IMG_DIR = DATA_DIR.joinpath("img")
|
||||
BACKUP_DIR = DATA_DIR.joinpath("backups")
|
||||
DEBUG_DIR = DATA_DIR.joinpath("debug")
|
||||
MIGRATION_DIR = DATA_DIR.joinpath("migration")
|
||||
NEXTCLOUD_DIR = MIGRATION_DIR.joinpath("nextcloud")
|
||||
CHOWDOWN_DIR = MIGRATION_DIR.joinpath("chowdown")
|
||||
TEMPLATE_DIR = DATA_DIR.joinpath("templates")
|
||||
SQLITE_DIR = DATA_DIR.joinpath("db")
|
||||
TEMP_DIR = DATA_DIR.joinpath(".temp")
|
||||
|
||||
REQUIRED_DIRS = [
|
||||
DATA_DIR,
|
||||
IMG_DIR,
|
||||
BACKUP_DIR,
|
||||
DEBUG_DIR,
|
||||
MIGRATION_DIR,
|
||||
TEMPLATE_DIR,
|
||||
SQLITE_DIR,
|
||||
NEXTCLOUD_DIR,
|
||||
CHOWDOWN_DIR,
|
||||
]
|
||||
|
||||
ensure_dirs()
|
||||
|
||||
|
||||
# DATABASE ENV
|
||||
SQLITE_FILE = None
|
||||
DATABASE_TYPE = os.getenv("db_type", "sqlite")
|
||||
if DATABASE_TYPE == "sqlite":
|
||||
USE_SQL = True
|
||||
SQLITE_FILE = SQLITE_DIR.joinpath(f"mealie_{DB_VERSION}.sqlite")
|
||||
|
||||
else:
|
||||
raise Exception(
|
||||
"Unable to determine database type. Acceptible options are 'sqlite' "
|
||||
)
|
||||
|
||||
# Mongo Database
|
||||
MEALIE_DB_NAME = os.getenv("mealie_db_name", "mealie")
|
||||
DB_USERNAME = os.getenv("db_username", "root")
|
||||
DB_PASSWORD = os.getenv("db_password", "example")
|
||||
DB_HOST = os.getenv("db_host", "mongo")
|
||||
DB_PORT = os.getenv("db_port", 27017)
|
||||
|
||||
# SFTP Email Stuff - For use Later down the line!
|
||||
SFTP_USERNAME = os.getenv("sftp_username", None)
|
||||
SFTP_PASSWORD = os.getenv("sftp_password", None)
|
||||
109
mealie/core/config.py
Normal file
109
mealie/core/config.py
Normal file
@@ -0,0 +1,109 @@
|
||||
import os
|
||||
import secrets
|
||||
from pathlib import Path
|
||||
|
||||
import dotenv
|
||||
|
||||
APP_VERSION = "v0.4.0"
|
||||
DB_VERSION = "v0.4.0"
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
BASE_DIR = CWD.parent.parent
|
||||
|
||||
ENV = BASE_DIR.joinpath(".env")
|
||||
dotenv.load_dotenv(ENV)
|
||||
PRODUCTION = os.environ.get("ENV")
|
||||
|
||||
|
||||
def determine_data_dir(production: bool) -> Path:
|
||||
global CWD
|
||||
if production:
|
||||
return Path("/app/data")
|
||||
|
||||
return CWD.parent.parent.joinpath("dev", "data")
|
||||
|
||||
|
||||
def determine_secrets(data_dir: Path, production: bool) -> str:
|
||||
if not production:
|
||||
return "shh-secret-test-key"
|
||||
|
||||
secrets_file = data_dir.joinpath(".secret")
|
||||
if secrets_file.is_file():
|
||||
with open(secrets_file, "r") as f:
|
||||
return f.read()
|
||||
else:
|
||||
with open(secrets_file, "w") as f:
|
||||
new_secret = secrets.token_hex(32)
|
||||
f.write(new_secret)
|
||||
return new_secret
|
||||
|
||||
|
||||
class AppDirectories:
|
||||
def __init__(self, cwd, data_dir) -> None:
|
||||
self.DATA_DIR = data_dir
|
||||
self.WEB_PATH = cwd.joinpath("dist")
|
||||
self.IMG_DIR = data_dir.joinpath("img")
|
||||
self.BACKUP_DIR = data_dir.joinpath("backups")
|
||||
self.DEBUG_DIR = data_dir.joinpath("debug")
|
||||
self.MIGRATION_DIR = data_dir.joinpath("migration")
|
||||
self.NEXTCLOUD_DIR = self.MIGRATION_DIR.joinpath("nextcloud")
|
||||
self.CHOWDOWN_DIR = self.MIGRATION_DIR.joinpath("chowdown")
|
||||
self.TEMPLATE_DIR = data_dir.joinpath("templates")
|
||||
self.USER_DIR = data_dir.joinpath("users")
|
||||
self.SQLITE_DIR = data_dir.joinpath("db")
|
||||
self.RECIPE_DATA_DIR = data_dir.joinpath("recipes")
|
||||
self.TEMP_DIR = data_dir.joinpath(".temp")
|
||||
|
||||
self.ensure_directories()
|
||||
|
||||
def ensure_directories(self):
|
||||
required_dirs = [
|
||||
self.IMG_DIR,
|
||||
self.BACKUP_DIR,
|
||||
self.DEBUG_DIR,
|
||||
self.MIGRATION_DIR,
|
||||
self.TEMPLATE_DIR,
|
||||
self.SQLITE_DIR,
|
||||
self.NEXTCLOUD_DIR,
|
||||
self.CHOWDOWN_DIR,
|
||||
self.RECIPE_DATA_DIR,
|
||||
self.USER_DIR,
|
||||
]
|
||||
|
||||
for dir in required_dirs:
|
||||
dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
class AppSettings:
|
||||
def __init__(self, app_dirs: AppDirectories) -> None:
|
||||
global DB_VERSION
|
||||
self.PRODUCTION = bool(os.environ.get("ENV"))
|
||||
self.IS_DEMO = os.getenv("DEMO", "False") == "True"
|
||||
self.API_PORT = int(os.getenv("API_PORT", 9000))
|
||||
self.API = os.getenv("API_DOCS", "False") == "True"
|
||||
self.DOCS_URL = "/docs" if self.API else None
|
||||
self.REDOC_URL = "/redoc" if self.API else None
|
||||
self.SECRET = determine_secrets(app_dirs.DATA_DIR, self.PRODUCTION)
|
||||
self.DATABASE_TYPE = os.getenv("DB_TYPE", "sqlite")
|
||||
|
||||
# Used to Set SQLite File Version
|
||||
self.SQLITE_FILE = None
|
||||
if self.DATABASE_TYPE == "sqlite":
|
||||
self.SQLITE_FILE = app_dirs.SQLITE_DIR.joinpath(f"mealie_{DB_VERSION}.sqlite")
|
||||
else:
|
||||
raise Exception("Unable to determine database type. Acceptible options are 'sqlite'")
|
||||
|
||||
self.DEFAULT_GROUP = os.getenv("DEFAULT_GROUP", "Home")
|
||||
self.DEFAULT_PASSWORD = os.getenv("DEFAULT_PASSWORD", "MyPassword")
|
||||
|
||||
# Not Used!
|
||||
self.SFTP_USERNAME = os.getenv("SFTP_USERNAME", None)
|
||||
self.SFTP_PASSWORD = os.getenv("SFTP_PASSWORD", None)
|
||||
|
||||
|
||||
# General
|
||||
DATA_DIR = determine_data_dir(PRODUCTION)
|
||||
LOGGER_FILE = DATA_DIR.joinpath("mealie.log")
|
||||
|
||||
app_dirs = AppDirectories(CWD, DATA_DIR)
|
||||
settings = AppSettings(app_dirs)
|
||||
55
mealie/core/security.py
Normal file
55
mealie/core/security.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from datetime import datetime, timedelta
|
||||
from mealie.schema.user import UserInDB
|
||||
|
||||
from jose import jwt
|
||||
from mealie.core.config import settings
|
||||
from mealie.db.database import db
|
||||
from passlib.context import CryptContext
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
ALGORITHM = "HS256"
|
||||
|
||||
|
||||
def create_access_token(data: dict(), expires_delta: timedelta = None) -> str:
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=120)
|
||||
to_encode.update({"exp": expire})
|
||||
return jwt.encode(to_encode, settings.SECRET, algorithm=ALGORITHM)
|
||||
|
||||
|
||||
def authenticate_user(session, email: str, password: str) -> UserInDB:
|
||||
user: UserInDB = db.users.get(session, email, "email")
|
||||
if not user:
|
||||
return False
|
||||
if not verify_password(password, user.password):
|
||||
return False
|
||||
return user
|
||||
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
"""Compares a plain string to a hashed password
|
||||
|
||||
Args:
|
||||
plain_password (str): raw password string
|
||||
hashed_password (str): hashed password from the database
|
||||
|
||||
Returns:
|
||||
bool: Returns True if a match return False
|
||||
"""
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
"""Takes in a raw password and hashes it. Used prior to saving
|
||||
a new password to the database.
|
||||
|
||||
Args:
|
||||
password (str): Password String
|
||||
|
||||
Returns:
|
||||
str: Hashed Password
|
||||
"""
|
||||
return pwd_context.hash(password)
|
||||
@@ -1,21 +1,27 @@
|
||||
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 CustomPage, SiteSettings
|
||||
from mealie.db.models.sign_up import SignUp
|
||||
from mealie.db.models.theme import SiteThemeModel
|
||||
from mealie.db.models.users import User
|
||||
from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse
|
||||
from mealie.schema.meal import MealPlanInDB
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.schema.settings import CustomPageOut, 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.session import Session
|
||||
|
||||
from db.db_base import BaseDocument
|
||||
from db.sql.meal_models import MealPlanModel
|
||||
from db.sql.recipe_models import Category, RecipeModel, Tag
|
||||
from db.sql.settings_models import SiteSettingsModel
|
||||
from db.sql.theme_models import SiteThemeModel
|
||||
|
||||
"""
|
||||
# TODO
|
||||
- [ ] Abstract Classes to use save_new, and update from base models
|
||||
"""
|
||||
|
||||
|
||||
class _Recipes(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "slug"
|
||||
self.sql_model: RecipeModel = RecipeModel
|
||||
self.orm_mode = True
|
||||
self.schema: Recipe = Recipe
|
||||
|
||||
def update_image(self, session: Session, slug: str, extension: str = None) -> str:
|
||||
entry: RecipeModel = self._query_one(session, match_value=slug)
|
||||
@@ -29,30 +35,96 @@ class _Categories(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "slug"
|
||||
self.sql_model = Category
|
||||
self.orm_mode = True
|
||||
self.schema = RecipeCategoryResponse
|
||||
|
||||
|
||||
class _Tags(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "slug"
|
||||
self.sql_model = Tag
|
||||
self.orm_mode = True
|
||||
self.schema = RecipeTagResponse
|
||||
|
||||
|
||||
class _Meals(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "uid"
|
||||
self.sql_model = MealPlanModel
|
||||
self.orm_mode = True
|
||||
self.schema = MealPlanInDB
|
||||
|
||||
|
||||
class _Settings(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "name"
|
||||
self.sql_model = SiteSettingsModel
|
||||
self.primary_key = "id"
|
||||
self.sql_model = SiteSettings
|
||||
self.orm_mode = True
|
||||
self.schema = SiteSettingsSchema
|
||||
|
||||
|
||||
class _Themes(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "name"
|
||||
self.sql_model = SiteThemeModel
|
||||
self.orm_mode = True
|
||||
self.schema = SiteTheme
|
||||
|
||||
|
||||
class _Users(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = User
|
||||
self.orm_mode = True
|
||||
self.schema = UserInDB
|
||||
|
||||
def update_password(self, session, id, password: str):
|
||||
entry = self._query_one(session=session, match_value=id)
|
||||
entry.update_password(password)
|
||||
session.commit()
|
||||
|
||||
return self.schema.from_orm(entry)
|
||||
|
||||
|
||||
class _Groups(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = Group
|
||||
self.orm_mode = True
|
||||
self.schema = GroupInDB
|
||||
|
||||
def get_meals(self, session: Session, match_value: str, match_key: str = "name") -> list[MealPlanInDB]:
|
||||
"""A Helper function to get the group from the database and return a sorted list of
|
||||
|
||||
Args:
|
||||
session (Session): SqlAlchemy Session
|
||||
match_value (str): Match Value
|
||||
match_key (str, optional): Match Key. Defaults to "name".
|
||||
|
||||
Returns:
|
||||
list[MealPlanInDB]: [description]
|
||||
"""
|
||||
group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none()
|
||||
|
||||
# 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):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "token"
|
||||
self.sql_model = SignUp
|
||||
self.orm_mode = True
|
||||
self.schema = SignUpOut
|
||||
|
||||
|
||||
class _CustomPages(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = CustomPage
|
||||
self.orm_mode = True
|
||||
self.schema = CustomPageOut
|
||||
|
||||
|
||||
class Database:
|
||||
@@ -63,6 +135,10 @@ class Database:
|
||||
self.themes = _Themes()
|
||||
self.categories = _Categories()
|
||||
self.tags = _Tags()
|
||||
self.users = _Users()
|
||||
self.sign_ups = _SignUps()
|
||||
self.groups = _Groups()
|
||||
self.custom_pages = _CustomPages()
|
||||
|
||||
|
||||
db = Database()
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
from typing import List
|
||||
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import load_only
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from db.sql.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class BaseDocument:
|
||||
def __init__(self) -> None:
|
||||
self.primary_key: str
|
||||
self.store: str
|
||||
self.sql_model: SqlAlchemyBase
|
||||
self.orm_mode = False
|
||||
self.schema: BaseModel
|
||||
|
||||
# TODO: Improve Get All Query Functionality
|
||||
def get_all(
|
||||
self, session: Session, limit: int = None, order_by: str = None
|
||||
) -> List[dict]:
|
||||
list = [x.dict() for x in session.query(self.sql_model).limit(limit).all()]
|
||||
def get_all(self, session: Session, limit: int = None, order_by: str = None) -> List[dict]:
|
||||
|
||||
if limit == 1:
|
||||
return list[0]
|
||||
if self.orm_mode:
|
||||
return [self.schema.from_orm(x) for x in session.query(self.sql_model).limit(limit).all()]
|
||||
|
||||
return list
|
||||
# list = [x.dict() for x in session.query(self.sql_model).limit(limit).all()]
|
||||
|
||||
def get_all_limit_columns(
|
||||
self, session: Session, fields: List[str], limit: int = None
|
||||
) -> List[SqlAlchemyBase]:
|
||||
# if limit == 1:
|
||||
# return list[0]
|
||||
|
||||
# return list
|
||||
|
||||
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"
|
||||
|
||||
@@ -37,11 +39,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()
|
||||
)
|
||||
|
||||
return results
|
||||
return session.query(self.sql_model).options(load_only(*fields)).limit(limit).all()
|
||||
|
||||
def get_all_primary_keys(self, session: Session) -> List[str]:
|
||||
"""Queries the database of the selected model and returns a list
|
||||
@@ -53,15 +51,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.
|
||||
|
||||
@@ -72,18 +66,12 @@ class BaseDocument:
|
||||
Returns:
|
||||
Union[Session, SqlAlchemyBase]: Will return both the session and found model
|
||||
"""
|
||||
if match_key == None:
|
||||
if match_key is None:
|
||||
match_key = self.primary_key
|
||||
|
||||
result = (
|
||||
session.query(self.sql_model).filter_by(**{match_key: match_value}).one()
|
||||
)
|
||||
return 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) -> BaseModel or List[BaseModel]:
|
||||
"""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.
|
||||
|
||||
@@ -96,23 +84,19 @@ class BaseDocument:
|
||||
Returns:
|
||||
dict or list[dict]:
|
||||
"""
|
||||
if match_key == None:
|
||||
if match_key is None:
|
||||
match_key = self.primary_key
|
||||
|
||||
result = (
|
||||
session.query(self.sql_model)
|
||||
.filter_by(**{match_key: match_value})
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
db_entries = [x.dict() for x in result]
|
||||
result = session.query(self.sql_model).filter_by(**{match_key: match_value}).limit(limit).all()
|
||||
|
||||
if limit == 1:
|
||||
return db_entries[0]
|
||||
try:
|
||||
return self.schema.from_orm(result[0])
|
||||
except IndexError:
|
||||
return None
|
||||
return [self.schema.from_orm(x) for x in result]
|
||||
|
||||
return db_entries
|
||||
|
||||
def create(self, session: Session, document: dict) -> dict:
|
||||
def create(self, session: Session, document: dict) -> BaseModel:
|
||||
"""Creates a new database entry for the given SQL Alchemy Model.
|
||||
|
||||
Args: \n
|
||||
@@ -124,14 +108,12 @@ class BaseDocument:
|
||||
"""
|
||||
new_document = self.sql_model(session=session, **document)
|
||||
session.add(new_document)
|
||||
return_data = new_document.dict()
|
||||
session.commit()
|
||||
|
||||
return return_data
|
||||
return self.schema.from_orm(new_document)
|
||||
|
||||
def update(self, session: Session, match_value: str, new_data: str) -> dict:
|
||||
def update(self, session: Session, match_value: str, new_data: str) -> BaseModel:
|
||||
"""Update a database entry.
|
||||
|
||||
Args: \n
|
||||
session (Session): Database Session
|
||||
match_value (str): Match "key"
|
||||
@@ -143,17 +125,12 @@ class BaseDocument:
|
||||
|
||||
entry = self._query_one(session=session, match_value=match_value)
|
||||
entry.update(session=session, **new_data)
|
||||
return_data = entry.dict()
|
||||
session.commit()
|
||||
|
||||
return return_data
|
||||
session.commit()
|
||||
return self.schema.from_orm(entry)
|
||||
|
||||
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,15 +1,12 @@
|
||||
from app_config import SQLITE_FILE, USE_SQL
|
||||
from mealie.core.config import settings
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from db.sql.db_session import sql_global_init
|
||||
from mealie.db.models.db_session import sql_global_init
|
||||
|
||||
sql_exists = True
|
||||
|
||||
if USE_SQL:
|
||||
sql_exists = SQLITE_FILE.is_file()
|
||||
SessionLocal = sql_global_init(SQLITE_FILE)
|
||||
else:
|
||||
raise Exception("Cannot identify database type")
|
||||
sql_exists = settings.SQLITE_FILE.is_file()
|
||||
SessionLocal = sql_global_init(settings.SQLITE_FILE)
|
||||
|
||||
|
||||
def create_session() -> Session:
|
||||
|
||||
57
mealie/db/init_db.py
Normal file
57
mealie/db/init_db.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from fastapi.logger import logger
|
||||
from mealie.core.config import settings
|
||||
from mealie.core.security import get_password_hash
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session, sql_exists
|
||||
from mealie.schema.settings import SiteSettings
|
||||
from mealie.schema.theme import SiteTheme
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
def init_db(db: Session = None) -> None:
|
||||
if not db:
|
||||
db = create_session()
|
||||
|
||||
default_group_init(db)
|
||||
default_settings_init(db)
|
||||
default_theme_init(db)
|
||||
default_user_init(db)
|
||||
|
||||
db.close()
|
||||
|
||||
|
||||
def default_theme_init(session: Session):
|
||||
db.themes.create(session, SiteTheme().dict())
|
||||
|
||||
|
||||
def default_settings_init(session: Session):
|
||||
document = db.settings.create(session, SiteSettings().dict())
|
||||
logger.info(f"Created Site Settings: \n {document}")
|
||||
|
||||
|
||||
def default_group_init(session: Session):
|
||||
default_group = {"name": settings.DEFAULT_GROUP}
|
||||
logger.info("Generating Default Group")
|
||||
db.groups.create(session, default_group)
|
||||
|
||||
|
||||
def default_user_init(session: Session):
|
||||
default_user = {
|
||||
"full_name": "Change Me",
|
||||
"email": "changeme@email.com",
|
||||
"password": get_password_hash(settings.DEFAULT_PASSWORD),
|
||||
"group": settings.DEFAULT_GROUP,
|
||||
"admin": True,
|
||||
}
|
||||
|
||||
logger.info("Generating Default User")
|
||||
db.users.create(session, default_user)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if sql_exists:
|
||||
print("Database Exists")
|
||||
exit()
|
||||
else:
|
||||
print("Database Doesn't Exists, Initializing...")
|
||||
init_db()
|
||||
7
mealie/db/models/_all_models.py
Normal file
7
mealie/db/models/_all_models.py
Normal file
@@ -0,0 +1,7 @@
|
||||
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.sql.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.sql._all_models
|
||||
import mealie.db.models._all_models # noqa: F401
|
||||
|
||||
SqlAlchemyBase.metadata.create_all(engine)
|
||||
|
||||
61
mealie/db/models/group.py
Normal file
61
mealie/db/models/group.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.recipe.category import Category, group2categories
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
||||
class WebhookURLModel(SqlAlchemyBase):
|
||||
__tablename__ = "webhook_urls"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
url = sa.Column(sa.String)
|
||||
parent_id = sa.Column(sa.Integer, sa.ForeignKey("groups.id"))
|
||||
|
||||
|
||||
class Group(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "groups"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
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,
|
||||
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-orphan")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
id=None,
|
||||
users=None,
|
||||
mealplans=None,
|
||||
categories=[],
|
||||
session=None,
|
||||
webhook_enable=False,
|
||||
webhook_time="00:00",
|
||||
webhook_urls=[],
|
||||
) -> None:
|
||||
self.name = name
|
||||
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
|
||||
self.webhook_urls = [WebhookURLModel(url=x) for x in webhook_urls]
|
||||
|
||||
def update(self, session: Session, *args, **kwargs):
|
||||
|
||||
self.__init__(session=session, *args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_ref(session: Session, name: str):
|
||||
item = session.query(Group).filter(Group.name == name).one_or_none()
|
||||
if item is None:
|
||||
item = session.query(Group).filter(Group.id == 1).one()
|
||||
return item
|
||||
50
mealie/db/models/mealplan.py
Normal file
50
mealie/db/models/mealplan.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from typing import List
|
||||
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
|
||||
|
||||
class Meal(SqlAlchemyBase):
|
||||
__tablename__ = "meal"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("mealplan.uid"))
|
||||
slug = sa.Column(sa.String)
|
||||
name = sa.Column(sa.String)
|
||||
date = sa.Column(sa.Date)
|
||||
image = sa.Column(sa.String)
|
||||
description = sa.Column(sa.String)
|
||||
|
||||
def __init__(self, slug, name, date, image, description, session=None) -> None:
|
||||
self.slug = slug
|
||||
self.name = name
|
||||
self.date = date
|
||||
self.image = image
|
||||
self.description = description
|
||||
|
||||
|
||||
class MealPlanModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "mealplan"
|
||||
uid = sa.Column(sa.Integer, primary_key=True, unique=True) # ! Probably Bad?
|
||||
startDate = sa.Column(sa.Date)
|
||||
endDate = sa.Column(sa.Date)
|
||||
meals: List[Meal] = orm.relationship(Meal, cascade="all, delete, delete-orphan")
|
||||
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:
|
||||
self.startDate = startDate
|
||||
self.endDate = endDate
|
||||
self.group = Group.get_ref(session, group)
|
||||
self.meals = [Meal(**meal) for meal in meals]
|
||||
|
||||
def update(self, session, startDate, endDate, meals, uid, group) -> None:
|
||||
|
||||
self.__init__(
|
||||
startDate=startDate,
|
||||
endDate=endDate,
|
||||
meals=meals,
|
||||
group=group,
|
||||
session=session,
|
||||
)
|
||||
8
mealie/db/models/model_base.py
Normal file
8
mealie/db/models/model_base.py
Normal file
@@ -0,0 +1,8 @@
|
||||
import sqlalchemy.ext.declarative as dec
|
||||
|
||||
SqlAlchemyBase = dec.declarative_base()
|
||||
|
||||
|
||||
class BaseMixins:
|
||||
def _pass_on_me():
|
||||
pass
|
||||
14
mealie/db/models/recipe/api_extras.py
Normal file
14
mealie/db/models/recipe/api_extras.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import sqlalchemy as sa
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class ApiExtras(SqlAlchemyBase):
|
||||
__tablename__ = "api_extras"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||
key_name = sa.Column(sa.String, unique=True)
|
||||
value = sa.Column(sa.String)
|
||||
|
||||
def __init__(self, key, value) -> None:
|
||||
self.key_name = key
|
||||
self.value = value
|
||||
66
mealie/db/models/recipe/category.py
Normal file
66
mealie/db/models/recipe/category.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from fastapi.logger import logger
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from slugify import slugify
|
||||
from sqlalchemy.orm import validates
|
||||
|
||||
site_settings2categories = sa.Table(
|
||||
"site_settings2categoories",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("sidebar_id", sa.Integer, sa.ForeignKey("site_settings.id")),
|
||||
sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")),
|
||||
)
|
||||
|
||||
group2categories = sa.Table(
|
||||
"group2categories",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("group_id", sa.Integer, sa.ForeignKey("groups.id")),
|
||||
sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")),
|
||||
)
|
||||
|
||||
recipes2categories = sa.Table(
|
||||
"recipes2categories",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
||||
sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")),
|
||||
)
|
||||
|
||||
custom_pages2categories = sa.Table(
|
||||
"custom_pages2categories",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("custom_page_id", sa.Integer, sa.ForeignKey("custom_pages.id")),
|
||||
sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")),
|
||||
)
|
||||
|
||||
|
||||
class Category(SqlAlchemyBase):
|
||||
__tablename__ = "categories"
|
||||
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")
|
||||
|
||||
@validates("name")
|
||||
def validate_name(self, key, name):
|
||||
assert name != ""
|
||||
return name
|
||||
|
||||
def __init__(self, name, session=None) -> None:
|
||||
self.name = name.strip()
|
||||
self.slug = slugify(name)
|
||||
|
||||
@staticmethod
|
||||
def get_ref(session, slug: str):
|
||||
return session.query(Category).filter(Category.slug == slug).one()
|
||||
|
||||
@staticmethod
|
||||
def create_if_not_exist(session, name: str = None):
|
||||
test_slug = slugify(name)
|
||||
result = session.query(Category).filter(Category.slug == test_slug).one_or_none()
|
||||
if result:
|
||||
logger.info("Category exists, associating recipe")
|
||||
return result
|
||||
else:
|
||||
logger.info("Category doesn't exists, creating tag")
|
||||
return Category(name=name)
|
||||
13
mealie/db/models/recipe/ingredient.py
Normal file
13
mealie/db/models/recipe/ingredient.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sqlalchemy as sa
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class RecipeIngredient(SqlAlchemyBase):
|
||||
__tablename__ = "recipes_ingredients"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
position = sa.Column(sa.Integer)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||
ingredient = sa.Column(sa.String)
|
||||
|
||||
def update(self, ingredient):
|
||||
self.ingredient = ingredient
|
||||
11
mealie/db/models/recipe/instruction.py
Normal file
11
mealie/db/models/recipe/instruction.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import sqlalchemy as sa
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class RecipeInstruction(SqlAlchemyBase):
|
||||
__tablename__ = "recipe_instructions"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||
position = sa.Column(sa.Integer)
|
||||
type = sa.Column(sa.String, default="")
|
||||
text = sa.Column(sa.String)
|
||||
14
mealie/db/models/recipe/note.py
Normal file
14
mealie/db/models/recipe/note.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import sqlalchemy as sa
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class Note(SqlAlchemyBase):
|
||||
__tablename__ = "notes"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||
title = sa.Column(sa.String)
|
||||
text = sa.Column(sa.String)
|
||||
|
||||
def __init__(self, title, text) -> None:
|
||||
self.title = title
|
||||
self.text = text
|
||||
30
mealie/db/models/recipe/nutrition.py
Normal file
30
mealie/db/models/recipe/nutrition.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import sqlalchemy as sa
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class Nutrition(SqlAlchemyBase):
|
||||
__tablename__ = "recipe_nutrition"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||
calories = sa.Column(sa.String)
|
||||
fatContent = sa.Column(sa.String)
|
||||
fiberContent = sa.Column(sa.String)
|
||||
proteinContent = sa.Column(sa.String)
|
||||
sodiumContent = sa.Column(sa.String)
|
||||
sugarContent = sa.Column(sa.String)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
calories=None,
|
||||
fatContent=None,
|
||||
fiberContent=None,
|
||||
proteinContent=None,
|
||||
sodiumContent=None,
|
||||
sugarContent=None,
|
||||
) -> None:
|
||||
self.calories = calories
|
||||
self.fatContent = fatContent
|
||||
self.fiberContent = fiberContent
|
||||
self.proteinContent = proteinContent
|
||||
self.sodiumContent = sodiumContent
|
||||
self.sugarContent = sugarContent
|
||||
171
mealie/db/models/recipe/recipe.py
Normal file
171
mealie/db/models/recipe/recipe.py
Normal file
@@ -0,0 +1,171 @@
|
||||
import datetime
|
||||
from datetime import date
|
||||
from typing import List
|
||||
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
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
|
||||
|
||||
|
||||
class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "recipes"
|
||||
# Database Specific
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
|
||||
# General Recipe Properties
|
||||
name = sa.Column(sa.String, nullable=False)
|
||||
description = sa.Column(sa.String)
|
||||
image = sa.Column(sa.String)
|
||||
totalTime = sa.Column(sa.String)
|
||||
prepTime = sa.Column(sa.String)
|
||||
performTime = sa.Column(sa.String)
|
||||
cookTime = sa.Column(sa.String)
|
||||
recipeYield = sa.Column(sa.String)
|
||||
recipeCuisine = sa.Column(sa.String)
|
||||
tools: List[Tool] = orm.relationship("Tool", cascade="all, delete-orphan")
|
||||
nutrition: Nutrition = orm.relationship("Nutrition", uselist=False, cascade="all, delete-orphan")
|
||||
recipeCategory: List = orm.relationship("Category", secondary=recipes2categories, back_populates="recipes")
|
||||
|
||||
recipeIngredient: List[RecipeIngredient] = orm.relationship(
|
||||
"RecipeIngredient",
|
||||
cascade="all, delete-orphan",
|
||||
order_by="RecipeIngredient.position",
|
||||
collection_class=ordering_list("position"),
|
||||
)
|
||||
recipeInstructions: List[RecipeInstruction] = orm.relationship(
|
||||
"RecipeInstruction",
|
||||
cascade="all, delete-orphan",
|
||||
order_by="RecipeInstruction.position",
|
||||
collection_class=ordering_list("position"),
|
||||
)
|
||||
|
||||
# Mealie Specific
|
||||
slug = sa.Column(sa.String, index=True, unique=True)
|
||||
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-orphan")
|
||||
rating = sa.Column(sa.Integer)
|
||||
orgURL = sa.Column(sa.String)
|
||||
extras: List[ApiExtras] = orm.relationship("ApiExtras", cascade="all, delete-orphan")
|
||||
|
||||
@validates("name")
|
||||
def validate_name(self, key, name):
|
||||
assert not name == ""
|
||||
return name
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session,
|
||||
name: str = None,
|
||||
description: str = None,
|
||||
image: str = None,
|
||||
recipeYield: str = None,
|
||||
recipeIngredient: List[str] = None,
|
||||
recipeInstructions: List[dict] = None,
|
||||
recipeCuisine: str = None,
|
||||
totalTime: str = None,
|
||||
prepTime: str = None,
|
||||
nutrition: dict = None,
|
||||
tools: list[str] = [],
|
||||
performTime: str = None,
|
||||
slug: str = None,
|
||||
recipeCategory: List[str] = None,
|
||||
tags: List[str] = None,
|
||||
dateAdded: datetime.date = None,
|
||||
notes: List[dict] = None,
|
||||
rating: int = None,
|
||||
orgURL: str = None,
|
||||
extras: dict = None,
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.image = image
|
||||
self.recipeCuisine = recipeCuisine
|
||||
|
||||
if self.nutrition:
|
||||
self.nutrition = Nutrition(**nutrition)
|
||||
else:
|
||||
self.nutrition = Nutrition()
|
||||
|
||||
self.tools = [Tool(tool=x) for x in tools] if tools else []
|
||||
|
||||
self.recipeYield = recipeYield
|
||||
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
|
||||
]
|
||||
self.totalTime = totalTime
|
||||
self.prepTime = prepTime
|
||||
self.performTime = performTime
|
||||
|
||||
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]
|
||||
self.slug = slug
|
||||
self.dateAdded = dateAdded
|
||||
self.notes = [Note(**note) for note in notes]
|
||||
self.rating = rating
|
||||
self.orgURL = orgURL
|
||||
self.extras = [ApiExtras(key=key, value=value) for key, value in extras.items()]
|
||||
|
||||
def update(
|
||||
self,
|
||||
session,
|
||||
name: str = None,
|
||||
description: str = None,
|
||||
image: str = None,
|
||||
recipeYield: str = None,
|
||||
recipeIngredient: List[str] = None,
|
||||
recipeInstructions: List[dict] = None,
|
||||
recipeCuisine: str = None,
|
||||
totalTime: str = None,
|
||||
tools: list[str] = [],
|
||||
prepTime: str = None,
|
||||
performTime: str = None,
|
||||
nutrition: dict = None,
|
||||
slug: str = None,
|
||||
recipeCategory: List[str] = None,
|
||||
tags: List[str] = None,
|
||||
dateAdded: datetime.date = None,
|
||||
notes: List[dict] = None,
|
||||
rating: int = None,
|
||||
orgURL: str = None,
|
||||
extras: dict = None,
|
||||
):
|
||||
"""Updated a database entry by removing nested rows and rebuilds the row through the __init__ functions"""
|
||||
|
||||
self.__init__(
|
||||
session=session,
|
||||
name=name,
|
||||
description=description,
|
||||
image=image,
|
||||
recipeYield=recipeYield,
|
||||
recipeIngredient=recipeIngredient,
|
||||
recipeInstructions=recipeInstructions,
|
||||
totalTime=totalTime,
|
||||
recipeCuisine=recipeCuisine,
|
||||
prepTime=prepTime,
|
||||
performTime=performTime,
|
||||
nutrition=nutrition,
|
||||
tools=tools,
|
||||
slug=slug,
|
||||
recipeCategory=recipeCategory,
|
||||
tags=tags,
|
||||
dateAdded=dateAdded,
|
||||
notes=notes,
|
||||
rating=rating,
|
||||
orgURL=orgURL,
|
||||
extras=extras,
|
||||
)
|
||||
42
mealie/db/models/recipe/tag.py
Normal file
42
mealie/db/models/recipe/tag.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from fastapi.logger import logger
|
||||
from slugify import slugify
|
||||
from sqlalchemy.orm import validates
|
||||
|
||||
recipes2tags = sa.Table(
|
||||
"recipes2tags",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
||||
sa.Column("tag_slug", sa.Integer, sa.ForeignKey("tags.slug")),
|
||||
)
|
||||
|
||||
|
||||
class Tag(SqlAlchemyBase):
|
||||
__tablename__ = "tags"
|
||||
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")
|
||||
|
||||
@validates("name")
|
||||
def validate_name(self, key, name):
|
||||
assert name != ""
|
||||
return name
|
||||
|
||||
def __init__(self, name) -> None:
|
||||
self.name = name.strip()
|
||||
self.slug = slugify(self.name)
|
||||
|
||||
@staticmethod
|
||||
def create_if_not_exist(session, name: str = None):
|
||||
test_slug = slugify(name)
|
||||
result = session.query(Tag).filter(Tag.slug == test_slug).one_or_none()
|
||||
|
||||
if result:
|
||||
logger.info("Tag exists, associating recipe")
|
||||
return result
|
||||
else:
|
||||
logger.info("Tag doesn't exists, creating tag")
|
||||
return Tag(name=name)
|
||||
12
mealie/db/models/recipe/tool.py
Normal file
12
mealie/db/models/recipe/tool.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import sqlalchemy as sa
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class Tool(SqlAlchemyBase):
|
||||
__tablename__ = "tools"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||
tool = sa.Column(sa.String)
|
||||
|
||||
def __init__(self, tool) -> None:
|
||||
self.tool = tool
|
||||
57
mealie/db/models/settings.py
Normal file
57
mealie/db/models/settings.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.recipe.category import Category, custom_pages2categories, site_settings2categories
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
class SiteSettings(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "site_settings"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
language = sa.Column(sa.String)
|
||||
categories = orm.relationship(
|
||||
"Category",
|
||||
secondary=site_settings2categories,
|
||||
single_parent=True,
|
||||
)
|
||||
show_recent = sa.Column(sa.Boolean, default=True)
|
||||
cards_per_section = sa.Column(sa.Integer)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session: Session = None,
|
||||
language="en",
|
||||
categories: list = [],
|
||||
show_recent=True,
|
||||
cards_per_section: int = 9,
|
||||
) -> None:
|
||||
session.commit()
|
||||
self.language = language
|
||||
self.cards_per_section = cards_per_section
|
||||
self.show_recent = show_recent
|
||||
self.categories = [Category.get_ref(session=session, slug=cat.get("slug")) for cat in categories]
|
||||
|
||||
def update(self, *args, **kwarg):
|
||||
self.__init__(*args, **kwarg)
|
||||
|
||||
|
||||
class CustomPage(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "custom_pages"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
position = sa.Column(sa.Integer, nullable=False)
|
||||
name = sa.Column(sa.String, nullable=False)
|
||||
slug = sa.Column(sa.String, nullable=False)
|
||||
categories = orm.relationship(
|
||||
"Category",
|
||||
secondary=custom_pages2categories,
|
||||
single_parent=True,
|
||||
)
|
||||
|
||||
def __init__(self, session=None, name=None, slug=None, position=0, categories=[], *args, **kwargs) -> None:
|
||||
self.name = name
|
||||
self.slug = slug
|
||||
self.position = position
|
||||
self.categories = [Category.get_ref(session=session, slug=cat.get("slug")) for cat in categories]
|
||||
|
||||
def update(self, *args, **kwarg):
|
||||
self.__init__(*args, **kwarg)
|
||||
21
mealie/db/models/sign_up.py
Normal file
21
mealie/db/models/sign_up.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from sqlalchemy import Boolean, Column, Integer, String
|
||||
|
||||
|
||||
class SignUp(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "sign_ups"
|
||||
id = Column(Integer, primary_key=True)
|
||||
token = Column(String, nullable=False, index=True)
|
||||
name = Column(String, index=True)
|
||||
admin = Column(Boolean, default=False)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session,
|
||||
token,
|
||||
name,
|
||||
admin,
|
||||
) -> None:
|
||||
self.token = token
|
||||
self.name = name
|
||||
self.admin = admin
|
||||
@@ -1,6 +1,6 @@
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.sql.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class SiteThemeModel(SqlAlchemyBase):
|
||||
@@ -14,11 +14,7 @@ class SiteThemeModel(SqlAlchemyBase):
|
||||
|
||||
def update(self, session=None, name: str = None, colors: dict = None) -> dict:
|
||||
self.colors.update(**colors)
|
||||
return self.dict()
|
||||
|
||||
def dict(self):
|
||||
data = {"name": self.name, "colors": self.colors.dict()}
|
||||
return data
|
||||
return self
|
||||
|
||||
|
||||
class ThemeColorsModel(SqlAlchemyBase):
|
||||
@@ -50,15 +46,3 @@ class ThemeColorsModel(SqlAlchemyBase):
|
||||
self.info = info
|
||||
self.warning = warning
|
||||
self.error = error
|
||||
|
||||
def dict(self):
|
||||
data = {
|
||||
"primary": self.primary,
|
||||
"accent": self.accent,
|
||||
"secondary": self.secondary,
|
||||
"success": self.success,
|
||||
"info": self.info,
|
||||
"warning": self.warning,
|
||||
"error": self.error,
|
||||
}
|
||||
return data
|
||||
51
mealie/db/models/users.py
Normal file
51
mealie/db/models/users.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from mealie.core.config import settings
|
||||
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
|
||||
# class UserSettings(SqlAlchemyBase, BaseMixins):
|
||||
# __tablename__ = "user_settings"
|
||||
# id = Column(Integer, primary_key=True, index=True)
|
||||
# parent_id = Column(String, ForeignKey("users.id"))
|
||||
|
||||
|
||||
class User(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "users"
|
||||
id = Column(Integer, primary_key=True)
|
||||
full_name = Column(String, index=True)
|
||||
email = Column(String, unique=True, index=True)
|
||||
password = Column(String)
|
||||
group_id = Column(String, ForeignKey("groups.id"))
|
||||
group = orm.relationship("Group", back_populates="users")
|
||||
admin = Column(Boolean, default=False)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session,
|
||||
full_name,
|
||||
email,
|
||||
password,
|
||||
group: str = settings.DEFAULT_GROUP,
|
||||
admin=False,
|
||||
id=None,
|
||||
) -> None:
|
||||
|
||||
group = group or settings.DEFAULT_GROUP
|
||||
self.full_name = full_name
|
||||
self.email = email
|
||||
self.group = Group.get_ref(session, group)
|
||||
self.admin = admin
|
||||
self.password = password
|
||||
|
||||
def update(self, full_name, email, group, admin, session=None, id=None, password=None):
|
||||
self.full_name = full_name
|
||||
self.email = email
|
||||
self.group = Group.get_ref(session, group)
|
||||
self.admin = admin
|
||||
|
||||
if password:
|
||||
self.password = password
|
||||
|
||||
def update_password(self, password):
|
||||
self.password = password
|
||||
@@ -1,4 +0,0 @@
|
||||
from db.sql.meal_models import *
|
||||
from db.sql.recipe_models import *
|
||||
from db.sql.settings_models import *
|
||||
from db.sql.theme_models import *
|
||||
@@ -1,68 +0,0 @@
|
||||
import uuid
|
||||
from typing import List
|
||||
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.sql.model_base import BaseMixins, SqlAlchemyBase
|
||||
|
||||
|
||||
class Meal(SqlAlchemyBase):
|
||||
__tablename__ = "meal"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("mealplan.uid"))
|
||||
slug = sa.Column(sa.String)
|
||||
name = sa.Column(sa.String)
|
||||
date = sa.Column(sa.Date)
|
||||
dateText = sa.Column(sa.String)
|
||||
image = sa.Column(sa.String)
|
||||
description = sa.Column(sa.String)
|
||||
|
||||
def __init__(
|
||||
self, slug, name, date, dateText, image, description, session=None
|
||||
) -> None:
|
||||
self.slug = slug
|
||||
self.name = name
|
||||
self.date = date
|
||||
self.dateText = dateText
|
||||
self.image = image
|
||||
self.description = description
|
||||
|
||||
def dict(self) -> dict:
|
||||
data = {
|
||||
"slug": self.slug,
|
||||
"name": self.name,
|
||||
"date": self.date,
|
||||
"dateText": self.dateText,
|
||||
"image": self.image,
|
||||
"description": self.description,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class MealPlanModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "mealplan"
|
||||
uid = sa.Column(sa.Integer, primary_key=True, unique=True) #! Probably Bad?
|
||||
startDate = sa.Column(sa.Date)
|
||||
endDate = sa.Column(sa.Date)
|
||||
meals: List[Meal] = orm.relation(Meal)
|
||||
|
||||
def __init__(self, startDate, endDate, meals, uid=None, session=None) -> None:
|
||||
self.startDate = startDate
|
||||
self.endDate = endDate
|
||||
self.meals = [Meal(**meal) for meal in meals]
|
||||
|
||||
def update(self, session, startDate, endDate, meals, uid) -> None:
|
||||
MealPlanModel._sql_remove_list(session, [Meal], uid)
|
||||
|
||||
self.__init__(startDate, endDate, meals)
|
||||
|
||||
def dict(self) -> dict:
|
||||
data = {
|
||||
"uid": self.uid,
|
||||
"startDate": self.startDate,
|
||||
"endDate": self.endDate,
|
||||
"meals": [meal.dict() for meal in self.meals],
|
||||
}
|
||||
|
||||
return data
|
||||
@@ -1,22 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
import sqlalchemy.ext.declarative as dec
|
||||
|
||||
SqlAlchemyBase = dec.declarative_base()
|
||||
|
||||
|
||||
class BaseMixins:
|
||||
@staticmethod
|
||||
def _sql_remove_list(session, list_of_tables: list, parent_id):
|
||||
|
||||
for table in list_of_tables:
|
||||
session.query(table).filter_by(parent_id=parent_id).delete()
|
||||
|
||||
@staticmethod
|
||||
def _flatten_dict(list_of_dict: List[dict]):
|
||||
finalMap = {}
|
||||
for d in list_of_dict:
|
||||
|
||||
finalMap.update(d.dict())
|
||||
|
||||
return finalMap
|
||||
@@ -1,352 +0,0 @@
|
||||
import datetime
|
||||
from datetime import date
|
||||
from typing import List
|
||||
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.sql.model_base import BaseMixins, SqlAlchemyBase
|
||||
from slugify import slugify
|
||||
from sqlalchemy.ext.orderinglist import ordering_list
|
||||
from sqlalchemy.orm import validates
|
||||
from utils.logger import logger
|
||||
|
||||
|
||||
class ApiExtras(SqlAlchemyBase):
|
||||
__tablename__ = "api_extras"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||
key_name = sa.Column(sa.String, unique=True)
|
||||
value = sa.Column(sa.String)
|
||||
|
||||
def __init__(self, key, value) -> None:
|
||||
self.key_name = key
|
||||
self.value = value
|
||||
|
||||
def dict(self):
|
||||
return {self.key_name: self.value}
|
||||
|
||||
|
||||
recipes2categories = sa.Table(
|
||||
"recipes2categories",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
||||
sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")),
|
||||
)
|
||||
|
||||
recipes2tags = sa.Table(
|
||||
"recipes2tags",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
||||
sa.Column("tag_slug", sa.Integer, sa.ForeignKey("tags.slug")),
|
||||
)
|
||||
|
||||
|
||||
class Category(SqlAlchemyBase):
|
||||
__tablename__ = "categories"
|
||||
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="categories"
|
||||
)
|
||||
|
||||
@validates("name")
|
||||
def validate_name(self, key, name):
|
||||
assert not name == ""
|
||||
return name
|
||||
|
||||
def __init__(self, name) -> None:
|
||||
self.name = name.strip()
|
||||
self.slug = slugify(name)
|
||||
|
||||
@staticmethod
|
||||
def create_if_not_exist(session, name: str = None):
|
||||
test_slug = slugify(name)
|
||||
try:
|
||||
result = session.query(Category).filter(Category.slug == test_slug).one()
|
||||
if result:
|
||||
logger.info("Category exists, associating recipe")
|
||||
return result
|
||||
else:
|
||||
logger.info("Category doesn't exists, creating tag")
|
||||
return Category(name=name)
|
||||
except:
|
||||
logger.info("Category doesn't exists, creating category")
|
||||
return Category(name=name)
|
||||
|
||||
def to_str(self):
|
||||
return self.name
|
||||
|
||||
def dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"slug": self.slug,
|
||||
"name": self.name,
|
||||
"recipes": [x.dict() for x in self.recipes],
|
||||
}
|
||||
|
||||
def dict_no_recipes(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"slug": self.slug,
|
||||
"name": self.name,
|
||||
}
|
||||
|
||||
|
||||
class Tag(SqlAlchemyBase):
|
||||
__tablename__ = "tags"
|
||||
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"
|
||||
)
|
||||
|
||||
@validates("name")
|
||||
def validate_name(self, key, name):
|
||||
assert not name == ""
|
||||
return name
|
||||
|
||||
def to_str(self):
|
||||
return self.name
|
||||
|
||||
def __init__(self, name) -> None:
|
||||
self.name = name.strip()
|
||||
self.slug = slugify(self.name)
|
||||
|
||||
def dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"slug": self.slug,
|
||||
"name": self.name,
|
||||
"recipes": [x.dict() for x in self.recipes],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def create_if_not_exist(session, name: str = None):
|
||||
test_slug = slugify(name)
|
||||
try:
|
||||
result = session.query(Tag).filter(Tag.slug == test_slug).first()
|
||||
|
||||
if result:
|
||||
logger.info("Tag exists, associating recipe")
|
||||
|
||||
return result
|
||||
else:
|
||||
logger.info("Tag doesn't exists, creating tag")
|
||||
return Tag(name=name)
|
||||
except:
|
||||
logger.info("Tag doesn't exists, creating tag")
|
||||
return Tag(name=name)
|
||||
|
||||
|
||||
class Note(SqlAlchemyBase):
|
||||
__tablename__ = "notes"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||
title = sa.Column(sa.String)
|
||||
text = sa.Column(sa.String)
|
||||
|
||||
def __init__(self, title, text) -> None:
|
||||
self.title = title
|
||||
self.text = text
|
||||
|
||||
def dict(self):
|
||||
return {"title": self.title, "text": self.text}
|
||||
|
||||
|
||||
class RecipeIngredient(SqlAlchemyBase):
|
||||
__tablename__ = "recipes_ingredients"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
position = sa.Column(sa.Integer)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||
ingredient = sa.Column(sa.String)
|
||||
|
||||
def update(self, ingredient):
|
||||
self.ingredient = ingredient
|
||||
|
||||
def to_str(self):
|
||||
return self.ingredient
|
||||
|
||||
|
||||
class RecipeInstruction(SqlAlchemyBase):
|
||||
__tablename__ = "recipe_instructions"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||
position = sa.Column(sa.Integer)
|
||||
type = sa.Column(sa.String, default="")
|
||||
text = sa.Column(sa.String)
|
||||
|
||||
def dict(self):
|
||||
data = {"@type": self.type, "text": self.text}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "recipes"
|
||||
# Database Specific
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
|
||||
# General Recipe Properties
|
||||
name = sa.Column(sa.String, nullable=False)
|
||||
description = sa.Column(sa.String)
|
||||
image = sa.Column(sa.String)
|
||||
recipeYield = sa.Column(sa.String)
|
||||
recipeIngredient: List[RecipeIngredient] = orm.relationship(
|
||||
"RecipeIngredient",
|
||||
cascade="all, delete",
|
||||
order_by="RecipeIngredient.position",
|
||||
collection_class=ordering_list("position"),
|
||||
)
|
||||
recipeInstructions: List[RecipeInstruction] = orm.relationship(
|
||||
"RecipeInstruction",
|
||||
cascade="all, delete",
|
||||
order_by="RecipeInstruction.position",
|
||||
collection_class=ordering_list("position"),
|
||||
)
|
||||
|
||||
# How to Properties
|
||||
totalTime = sa.Column(sa.String)
|
||||
prepTime = sa.Column(sa.String)
|
||||
performTime = sa.Column(sa.String)
|
||||
|
||||
# Mealie Specific
|
||||
slug = sa.Column(sa.String, index=True, unique=True)
|
||||
categories: List = orm.relationship(
|
||||
"Category", secondary=recipes2categories, 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)
|
||||
orgURL = sa.Column(sa.String)
|
||||
extras: List[ApiExtras] = orm.relationship("ApiExtras", cascade="all, delete")
|
||||
|
||||
@validates("name")
|
||||
def validate_name(self, key, name):
|
||||
assert not name == ""
|
||||
return name
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session,
|
||||
name: str = None,
|
||||
description: str = None,
|
||||
image: str = None,
|
||||
recipeYield: str = None,
|
||||
recipeIngredient: List[str] = None,
|
||||
recipeInstructions: List[dict] = None,
|
||||
totalTime: str = None,
|
||||
prepTime: str = None,
|
||||
performTime: str = None,
|
||||
slug: str = None,
|
||||
categories: List[str] = None,
|
||||
tags: List[str] = None,
|
||||
dateAdded: datetime.date = None,
|
||||
notes: List[dict] = None,
|
||||
rating: int = None,
|
||||
orgURL: str = None,
|
||||
extras: dict = None,
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.image = image
|
||||
self.recipeYield = recipeYield
|
||||
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
|
||||
]
|
||||
self.totalTime = totalTime
|
||||
self.prepTime = prepTime
|
||||
self.performTime = performTime
|
||||
|
||||
# Mealie Specific
|
||||
self.slug = slug
|
||||
self.categories = [
|
||||
Category.create_if_not_exist(session=session, name=cat)
|
||||
for cat in categories
|
||||
]
|
||||
|
||||
self.tags = [Tag.create_if_not_exist(session=session, name=tag) for tag in tags]
|
||||
|
||||
self.dateAdded = dateAdded
|
||||
self.notes = [Note(**note) for note in notes]
|
||||
self.rating = rating
|
||||
self.orgURL = orgURL
|
||||
self.extras = [ApiExtras(key=key, value=value) for key, value in extras.items()]
|
||||
|
||||
def update(
|
||||
self,
|
||||
session,
|
||||
name: str = None,
|
||||
description: str = None,
|
||||
image: str = None,
|
||||
recipeYield: str = None,
|
||||
recipeIngredient: List[str] = None,
|
||||
recipeInstructions: List[dict] = None,
|
||||
totalTime: str = None,
|
||||
prepTime: str = None,
|
||||
performTime: str = None,
|
||||
slug: str = None,
|
||||
categories: List[str] = None,
|
||||
tags: List[str] = None,
|
||||
dateAdded: datetime.date = None,
|
||||
notes: List[dict] = None,
|
||||
rating: int = None,
|
||||
orgURL: str = None,
|
||||
extras: dict = None,
|
||||
):
|
||||
"""Updated a database entry by removing nested rows and rebuilds the row through the __init__ functions"""
|
||||
list_of_tables = [RecipeIngredient, RecipeInstruction, ApiExtras]
|
||||
RecipeModel._sql_remove_list(session, list_of_tables, self.id)
|
||||
|
||||
self.__init__(
|
||||
session=session,
|
||||
name=name,
|
||||
description=description,
|
||||
image=image,
|
||||
recipeYield=recipeYield,
|
||||
recipeIngredient=recipeIngredient,
|
||||
recipeInstructions=recipeInstructions,
|
||||
totalTime=totalTime,
|
||||
prepTime=prepTime,
|
||||
performTime=performTime,
|
||||
slug=slug,
|
||||
categories=categories,
|
||||
tags=tags,
|
||||
dateAdded=dateAdded,
|
||||
notes=notes,
|
||||
rating=rating,
|
||||
orgURL=orgURL,
|
||||
extras=extras,
|
||||
)
|
||||
|
||||
def dict(self):
|
||||
data = {
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"image": self.image,
|
||||
"recipeYield": self.recipeYield,
|
||||
"recipeIngredient": [x.to_str() for x in self.recipeIngredient],
|
||||
"recipeInstructions": [x.dict() for x in self.recipeInstructions],
|
||||
"totalTime": self.totalTime,
|
||||
"prepTime": self.prepTime,
|
||||
"performTime": self.performTime,
|
||||
# Mealie
|
||||
"slug": self.slug,
|
||||
"categories": [x.to_str() for x in self.categories],
|
||||
"tags": [x.to_str() for x in self.tags],
|
||||
"dateAdded": self.dateAdded,
|
||||
"notes": [x.dict() for x in self.notes],
|
||||
"rating": self.rating,
|
||||
"orgURL": self.orgURL,
|
||||
"extras": RecipeModel._flatten_dict(self.extras),
|
||||
}
|
||||
|
||||
return data
|
||||
@@ -1,94 +0,0 @@
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.sql.model_base import BaseMixins, SqlAlchemyBase
|
||||
from db.sql.recipe_models import Category
|
||||
|
||||
|
||||
class SiteSettingsModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "site_settings"
|
||||
name = sa.Column(sa.String, primary_key=True)
|
||||
planCategories = orm.relationship(
|
||||
"MealCategory", uselist=True, cascade="all, delete"
|
||||
)
|
||||
webhooks = orm.relationship("WebHookModel", uselist=False, cascade="all, delete")
|
||||
|
||||
def __init__(
|
||||
self, name: str = None, webhooks: dict = None, planCategories=[], session=None
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.planCategories = [MealCategory(cat) for cat in planCategories]
|
||||
self.webhooks = WebHookModel(**webhooks)
|
||||
|
||||
def update(self, session, name, webhooks: dict, planCategories=[]) -> dict:
|
||||
|
||||
self._sql_remove_list(session, [MealCategory], self.name)
|
||||
self.name = name
|
||||
self.planCategories = [MealCategory(x) for x in planCategories]
|
||||
self.webhooks.update(session=session, **webhooks)
|
||||
return
|
||||
|
||||
def dict(self):
|
||||
data = {
|
||||
"name": self.name,
|
||||
"planCategories": [cat.to_str() for cat in self.planCategories],
|
||||
"webhooks": self.webhooks.dict(),
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
class MealCategory(SqlAlchemyBase):
|
||||
__tablename__ = "meal_plan_categories"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
name = sa.Column(sa.String)
|
||||
parent_id = sa.Column(sa.Integer, sa.ForeignKey("site_settings.name"))
|
||||
|
||||
def __init__(self, name) -> None:
|
||||
self.name = name
|
||||
|
||||
def to_str(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class WebHookModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "webhook_settings"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("site_settings.name"))
|
||||
webhookURLs = orm.relationship(
|
||||
"WebhookURLModel", uselist=True, cascade="all, delete"
|
||||
)
|
||||
webhookTime = sa.Column(sa.String, default="00:00")
|
||||
enabled = sa.Column(sa.Boolean, default=False)
|
||||
|
||||
def __init__(
|
||||
self, webhookURLs: list, webhookTime: str, enabled: bool = False, session=None
|
||||
) -> None:
|
||||
|
||||
self.webhookURLs = [WebhookURLModel(url=x) for x in webhookURLs]
|
||||
self.webhookTime = webhookTime
|
||||
self.enabled = enabled
|
||||
|
||||
def update(
|
||||
self, session, webhookURLs: list, webhookTime: str, enabled: bool
|
||||
) -> None:
|
||||
|
||||
self._sql_remove_list(session, [WebhookURLModel], self.id)
|
||||
|
||||
self.__init__(webhookURLs, webhookTime, enabled)
|
||||
|
||||
def dict(self):
|
||||
data = {
|
||||
"webhookURLs": [url.to_str() for url in self.webhookURLs],
|
||||
"webhookTime": self.webhookTime,
|
||||
"enabled": self.enabled,
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
class WebhookURLModel(SqlAlchemyBase):
|
||||
__tablename__ = "webhook_urls"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
url = sa.Column(sa.String)
|
||||
parent_id = sa.Column(sa.Integer, sa.ForeignKey("webhook_settings.id"))
|
||||
|
||||
def to_str(self):
|
||||
return self.url
|
||||
@@ -1,14 +0,0 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic.main import BaseModel
|
||||
from services.recipe_services import Recipe
|
||||
|
||||
|
||||
class RecipeCategoryResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
slug: str
|
||||
recipes: Optional[List[Recipe]]
|
||||
|
||||
class Config:
|
||||
schema_extra = {"example": {"id": 1, "name": "dinner", "recipes": [{}]}}
|
||||
@@ -1,20 +0,0 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic.main import BaseModel
|
||||
|
||||
|
||||
class RecipeImport(BaseModel):
|
||||
name: Optional[str]
|
||||
slug: str
|
||||
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]
|
||||
@@ -1,38 +0,0 @@
|
||||
from datetime import date
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Meal(BaseModel):
|
||||
slug: Optional[str]
|
||||
name: Optional[str]
|
||||
date: date
|
||||
dateText: str
|
||||
image: Optional[str]
|
||||
description: Optional[str]
|
||||
|
||||
|
||||
class MealData(BaseModel):
|
||||
name: Optional[str]
|
||||
slug: str
|
||||
dateText: str
|
||||
|
||||
|
||||
class MealPlan(BaseModel):
|
||||
uid: Optional[str]
|
||||
startDate: date
|
||||
endDate: date
|
||||
meals: List[Meal]
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"startDate": date.today(),
|
||||
"endDate": date.today(),
|
||||
"meals": [
|
||||
{"slug": "Packed Mac and Cheese", "date": date.today()},
|
||||
{"slug": "Eggs and Toast", "date": date.today()},
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Webhooks(BaseModel):
|
||||
webhookTime: str = "00:00"
|
||||
webhookURLs: Optional[List[str]] = []
|
||||
enabled: bool = False
|
||||
|
||||
|
||||
class SiteSettings(BaseModel):
|
||||
name: str = "main"
|
||||
planCategories: list[str] = []
|
||||
webhooks: Webhooks
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"name": "main",
|
||||
"planCategories": ["dinner", "lunch"],
|
||||
"webhooks": {
|
||||
"webhookTime": "00:00",
|
||||
"webhookURLs": ["https://mywebhookurl.com/webhook"],
|
||||
"enable": False,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,29 @@
|
||||
import operator
|
||||
import shutil
|
||||
|
||||
from app_config import BACKUP_DIR, TEMPLATE_DIR
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||
from models.backup_models import BackupJob, ImportJob, Imports, LocalBackup
|
||||
from services.backups.exports import backup_all
|
||||
from services.backups.imports import ImportDatabase
|
||||
from mealie.core.config import app_dirs
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from mealie.services.backups import imports
|
||||
from mealie.services.backups.exports import backup_all
|
||||
from sqlalchemy.orm.session import Session
|
||||
from starlette.responses import FileResponse
|
||||
from utils.snackbar import SnackResponse
|
||||
|
||||
router = APIRouter(prefix="/api/backups", tags=["Backups"])
|
||||
router = APIRouter(prefix="/api/backups", tags=["Backups"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("/available", response_model=Imports)
|
||||
def available_imports():
|
||||
"""Returns a list of avaiable .zip files for import into Mealie."""
|
||||
imports = []
|
||||
templates = []
|
||||
for archive in BACKUP_DIR.glob("*.zip"):
|
||||
for archive in app_dirs.BACKUP_DIR.glob("*.zip"):
|
||||
backup = LocalBackup(name=archive.name, date=archive.stat().st_ctime)
|
||||
imports.append(backup)
|
||||
|
||||
for template in TEMPLATE_DIR.glob("*.*"):
|
||||
templates.append(template.name)
|
||||
|
||||
templates = [template.name for template in app_dirs.TEMPLATE_DIR.glob("*.*")]
|
||||
imports.sort(key=operator.attrgetter("date"), reverse=True)
|
||||
|
||||
return Imports(imports=imports, templates=templates)
|
||||
@@ -40,7 +38,10 @@ def export_database(data: BackupJob, session: Session = Depends(generate_session
|
||||
templates=data.templates,
|
||||
export_recipes=data.options.recipes,
|
||||
export_settings=data.options.settings,
|
||||
export_pages=data.options.pages,
|
||||
export_themes=data.options.themes,
|
||||
export_users=data.options.users,
|
||||
export_groups=data.options.groups,
|
||||
)
|
||||
try:
|
||||
return SnackResponse.success("Backup Created at " + export_path)
|
||||
@@ -52,9 +53,9 @@ def export_database(data: BackupJob, session: Session = Depends(generate_session
|
||||
|
||||
|
||||
@router.post("/upload")
|
||||
def upload_backup_zipfile(archive: UploadFile = File(...)):
|
||||
def upload_backup_file(archive: UploadFile = File(...)):
|
||||
""" Upload a .zip File to later be imported into Mealie """
|
||||
dest = BACKUP_DIR.joinpath(archive.filename)
|
||||
dest = app_dirs.BACKUP_DIR.joinpath(archive.filename)
|
||||
|
||||
with dest.open("wb") as buffer:
|
||||
shutil.copyfileobj(archive.file, buffer)
|
||||
@@ -66,44 +67,40 @@ def upload_backup_zipfile(archive: UploadFile = File(...)):
|
||||
|
||||
|
||||
@router.get("/{file_name}/download")
|
||||
async def upload_nextcloud_zipfile(file_name: str):
|
||||
async def download_backup_file(file_name: str):
|
||||
""" Upload a .zip File to later be imported into Mealie """
|
||||
file = BACKUP_DIR.joinpath(file_name)
|
||||
file = app_dirs.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(
|
||||
return imports.import_database(
|
||||
session=session,
|
||||
zip_archive=import_data.name,
|
||||
archive=import_data.name,
|
||||
import_recipes=import_data.recipes,
|
||||
import_settings=import_data.settings,
|
||||
import_pages=import_data.pages,
|
||||
import_themes=import_data.themes,
|
||||
import_users=import_data.users,
|
||||
import_groups=import_data.groups,
|
||||
force_import=import_data.force,
|
||||
rebase=import_data.rebase,
|
||||
import_settings=import_data.settings,
|
||||
import_themes=import_data.themes,
|
||||
)
|
||||
|
||||
imported = import_db.run()
|
||||
return imported
|
||||
|
||||
|
||||
@router.delete("/{file_name}/delete", status_code=200)
|
||||
def delete_backup(file_name: str):
|
||||
""" Removes a database backup from the file system """
|
||||
|
||||
try:
|
||||
BACKUP_DIR.joinpath(file_name).unlink()
|
||||
app_dirs.BACKUP_DIR.joinpath(file_name).unlink()
|
||||
except:
|
||||
HTTPException(
|
||||
status_code=400,
|
||||
|
||||
@@ -1,35 +1,38 @@
|
||||
import json
|
||||
|
||||
from app_config import APP_VERSION, DEBUG_DIR
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import HTMLResponse
|
||||
from utils.logger import LOGGER_FILE
|
||||
from fastapi import APIRouter, Depends
|
||||
from mealie.core.config import APP_VERSION, LOGGER_FILE, app_dirs, settings
|
||||
from mealie.routes.deps import get_current_user
|
||||
|
||||
router = APIRouter(prefix="/api/debug", tags=["Debug"])
|
||||
|
||||
|
||||
@router.get("/version")
|
||||
async def get_mealie_version():
|
||||
async def get_mealie_version(current_user=Depends(get_current_user)):
|
||||
""" Returns the current version of mealie"""
|
||||
return {"version": APP_VERSION}
|
||||
|
||||
|
||||
@router.get("/is-demo")
|
||||
async def get_demo_status():
|
||||
print(settings.IS_DEMO)
|
||||
return {"demoStatus": settings.IS_DEMO}
|
||||
|
||||
|
||||
@router.get("/last-recipe-json")
|
||||
async def get_last_recipe_json():
|
||||
async def get_last_recipe_json(current_user=Depends(get_current_user)):
|
||||
""" Doc Str """
|
||||
|
||||
with open(DEBUG_DIR.joinpath("last_recipe.json"), "r") as f:
|
||||
with open(app_dirs.DEBUG_DIR.joinpath("last_recipe.json"), "r") as f:
|
||||
return json.loads(f.read())
|
||||
|
||||
|
||||
@router.get("/log/{num}")
|
||||
async def get_log(num: int):
|
||||
async def get_log(num: int, current_user=Depends(get_current_user)):
|
||||
""" Doc Str """
|
||||
with open(LOGGER_FILE, "rb") as f:
|
||||
log_text = tail(f, num)
|
||||
HTML_RESPONSE = log_text
|
||||
|
||||
return HTML_RESPONSE
|
||||
return log_text
|
||||
|
||||
|
||||
def tail(f, lines=20):
|
||||
|
||||
31
mealie/routes/deps.py
Normal file
31
mealie/routes/deps.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from jose import JWTError, jwt
|
||||
from mealie.core.config import settings
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.schema.auth import TokenData
|
||||
from mealie.schema.user import UserInDB
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token")
|
||||
ALGORITHM = "HS256"
|
||||
|
||||
|
||||
async def get_current_user(token: str = Depends(oauth2_scheme), session=Depends(generate_session)) -> UserInDB:
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
try:
|
||||
payload = jwt.decode(token, settings.SECRET, algorithms=[ALGORITHM])
|
||||
username: str = payload.get("sub")
|
||||
if username is None:
|
||||
raise credentials_exception
|
||||
token_data = TokenData(username=username)
|
||||
except JWTError:
|
||||
raise credentials_exception
|
||||
user = db.users.get(session, token_data.username, "email")
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
return user
|
||||
80
mealie/routes/groups/crud.py
Normal file
80
mealie/routes/groups/crud.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
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"])
|
||||
|
||||
|
||||
@router.get("", response_model=list[GroupInDB])
|
||||
async def get_all_groups(
|
||||
current_user=Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Returns a list of all groups in the database """
|
||||
|
||||
return db.groups.get_all(session)
|
||||
|
||||
|
||||
@router.get("/self", response_model=GroupInDB)
|
||||
async def get_current_user_group(
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Returns the Group Data for the Current User """
|
||||
current_user: UserInDB
|
||||
|
||||
return db.groups.get(session, current_user.group, "name")
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_group(
|
||||
group_data: GroupBase,
|
||||
current_user=Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Creates a Group in the Database """
|
||||
|
||||
try:
|
||||
db.groups.create(session, group_data.dict())
|
||||
return SnackResponse.success("User Group Created", {"created": True})
|
||||
except:
|
||||
return SnackResponse.error("User Group Creation Failed")
|
||||
|
||||
|
||||
@router.put("/{id}")
|
||||
async def update_group_data(
|
||||
id: int,
|
||||
group_data: UpdateGroup,
|
||||
current_user=Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Updates a User Group """
|
||||
db.groups.update(session, id, group_data.dict())
|
||||
|
||||
return SnackResponse.success("Group Settings Updated")
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_user_group(
|
||||
id: int, current_user=Depends(get_current_user), session: Session = Depends(generate_session)
|
||||
):
|
||||
""" Removes a user group from the database """
|
||||
|
||||
if id == 1:
|
||||
return SnackResponse.error("Cannot delete default group")
|
||||
|
||||
group: GroupInDB = db.groups.get(session, id)
|
||||
|
||||
if not group:
|
||||
return SnackResponse.error("Group not found")
|
||||
|
||||
if not group.users == []:
|
||||
return SnackResponse.error("Cannot delete group with users")
|
||||
|
||||
db.groups.delete(session, id)
|
||||
|
||||
return
|
||||
6
mealie/routes/groups/groups.py
Normal file
6
mealie/routes/groups/groups.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from fastapi import APIRouter
|
||||
from mealie.routes.groups import crud
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(crud.router)
|
||||
@@ -1,78 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from services.meal_services import MealPlan
|
||||
from sqlalchemy.orm.session import Session
|
||||
from utils.snackbar import SnackResponse
|
||||
|
||||
router = APIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
|
||||
|
||||
|
||||
@router.get("/all", response_model=List[MealPlan])
|
||||
def get_all_meals(session: Session = Depends(generate_session)):
|
||||
""" Returns a list of all available Meal Plan """
|
||||
|
||||
return MealPlan.get_all(session)
|
||||
|
||||
|
||||
@router.get("/{id}/shopping-list")
|
||||
def get_shopping_list(id: str, session: Session = Depends(generate_session)):
|
||||
|
||||
#! Refactor into Single Database Call
|
||||
mealplan = db.meals.get(session, id)
|
||||
slugs = [x.get("slug") for x in mealplan.get("meals")]
|
||||
recipes = [db.recipes.get(session, x) for x in slugs]
|
||||
ingredients = [
|
||||
{"name": x.get("name"), "recipeIngredient": x.get("recipeIngredient")}
|
||||
for x in recipes
|
||||
]
|
||||
|
||||
return ingredients
|
||||
|
||||
|
||||
@router.post("/create")
|
||||
def set_meal_plan(data: MealPlan, session: Session = Depends(generate_session)):
|
||||
""" Creates a meal plan database entry """
|
||||
data.process_meals(session)
|
||||
data.save_to_db(session)
|
||||
|
||||
return SnackResponse.success("Mealplan Created")
|
||||
|
||||
|
||||
@router.get("/this-week", response_model=MealPlan)
|
||||
def get_this_week(session: Session = Depends(generate_session)):
|
||||
""" Returns the meal plan data for this week """
|
||||
|
||||
return MealPlan.this_week(session)
|
||||
|
||||
|
||||
@router.put("/{plan_id}")
|
||||
def update_meal_plan(
|
||||
plan_id: str, meal_plan: MealPlan, session: Session = Depends(generate_session)
|
||||
):
|
||||
""" Updates a meal plan based off ID """
|
||||
meal_plan.process_meals(session)
|
||||
meal_plan.update(session, plan_id)
|
||||
|
||||
return SnackResponse.info("Mealplan Updated")
|
||||
|
||||
|
||||
@router.delete("/{plan_id}")
|
||||
def delete_meal_plan(plan_id, session: Session = Depends(generate_session)):
|
||||
""" Removes a meal plan from the database """
|
||||
|
||||
MealPlan.delete(session, plan_id)
|
||||
|
||||
return SnackResponse.error("Mealplan Deleted")
|
||||
|
||||
|
||||
@router.get("/today", tags=["Meal Plan"])
|
||||
def get_today(session: Session = Depends(generate_session)):
|
||||
"""
|
||||
Returns the recipe slug for the meal scheduled for today.
|
||||
If no meal is scheduled nothing is returned
|
||||
"""
|
||||
|
||||
return MealPlan.today(session)
|
||||
76
mealie/routes/mealplans/crud.py
Normal file
76
mealie/routes/mealplans/crud.py
Normal file
@@ -0,0 +1,76 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
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"])
|
||||
|
||||
|
||||
@router.get("/all", response_model=list[MealPlanInDB])
|
||||
def get_all_meals(
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Returns a list of all available Meal Plan """
|
||||
|
||||
return db.groups.get_meals(session, current_user.group)
|
||||
|
||||
|
||||
@router.post("/create")
|
||||
def create_meal_plan(
|
||||
data: MealPlanIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
||||
):
|
||||
""" Creates a meal plan database entry """
|
||||
processed_plan = process_meals(session, data)
|
||||
db.meals.create(session, processed_plan.dict())
|
||||
|
||||
return SnackResponse.success("Mealplan Created")
|
||||
|
||||
|
||||
@router.put("/{plan_id}")
|
||||
def update_meal_plan(
|
||||
plan_id: str,
|
||||
meal_plan: MealPlanIn,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
""" Updates a meal plan based off ID """
|
||||
processed_plan = process_meals(session, meal_plan)
|
||||
processed_plan = MealPlanInDB(uid=plan_id, **processed_plan.dict())
|
||||
db.meals.update(session, plan_id, processed_plan.dict())
|
||||
|
||||
return SnackResponse.info("Mealplan Updated")
|
||||
|
||||
|
||||
@router.delete("/{plan_id}")
|
||||
def delete_meal_plan(plan_id, session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
|
||||
""" Removes a meal plan from the database """
|
||||
|
||||
db.meals.delete(session, plan_id)
|
||||
|
||||
return SnackResponse.error("Mealplan Deleted")
|
||||
|
||||
|
||||
@router.get("/this-week", response_model=MealPlanInDB)
|
||||
def get_this_week(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)):
|
||||
""" Returns the meal plan data for this week """
|
||||
|
||||
return db.groups.get_meals(session, current_user.group)[0]
|
||||
|
||||
|
||||
@router.get("/today", tags=["Meal Plan"])
|
||||
def get_today(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)):
|
||||
"""
|
||||
Returns the recipe slug for the meal scheduled for today.
|
||||
If no meal is scheduled nothing is returned
|
||||
"""
|
||||
|
||||
group_in_db: GroupInDB = db.groups.get(session, current_user.group, "name")
|
||||
recipe = get_todays_meal(session, group_in_db)
|
||||
|
||||
return recipe.slug
|
||||
24
mealie/routes/mealplans/helpers.py
Normal file
24
mealie/routes/mealplans/helpers.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
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"])
|
||||
|
||||
|
||||
@router.get("/{id}/shopping-list")
|
||||
def get_shopping_list(
|
||||
id: str,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
|
||||
# ! Refactor into Single Database Call
|
||||
mealplan = db.meals.get(session, id)
|
||||
mealplan: MealPlanInDB
|
||||
slugs = [x.slug for x in mealplan.meals]
|
||||
recipes: list[Recipe] = [db.recipes.get(session, x) for x in slugs]
|
||||
return [{"name": x.name, "recipeIngredient": x.recipeIngredient} for x in recipes if x]
|
||||
7
mealie/routes/mealplans/mealplans.py
Normal file
7
mealie/routes/mealplans/mealplans.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from fastapi import APIRouter
|
||||
from mealie.routes.mealplans import crud, helpers
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(crud.router)
|
||||
router.include_router(helpers.router)
|
||||
@@ -2,16 +2,17 @@ import operator
|
||||
import shutil
|
||||
from typing import List
|
||||
|
||||
from app_config import MIGRATION_DIR
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||
from models.migration_models import MigrationFile, Migrations
|
||||
from services.migrations.chowdown import chowdown_migrate as chowdow_migrate
|
||||
from services.migrations.nextcloud import migrate as nextcloud_migrate
|
||||
from fastapi import APIRouter, Depends, File, UploadFile
|
||||
from mealie.core.config import app_dirs
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
from mealie.schema.migration import MigrationFile, Migrations
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
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 utils.snackbar import SnackResponse
|
||||
|
||||
router = APIRouter(prefix="/api/migrations", tags=["Migration"])
|
||||
router = APIRouter(prefix="/api/migrations", tags=["Migration"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("", response_model=List[Migrations])
|
||||
@@ -19,8 +20,8 @@ def get_avaiable_nextcloud_imports():
|
||||
""" Returns a list of avaiable directories that can be imported into Mealie """
|
||||
response_data = []
|
||||
migration_dirs = [
|
||||
MIGRATION_DIR.joinpath("nextcloud"),
|
||||
MIGRATION_DIR.joinpath("chowdown"),
|
||||
app_dirs.MIGRATION_DIR.joinpath("nextcloud"),
|
||||
app_dirs.MIGRATION_DIR.joinpath("chowdown"),
|
||||
]
|
||||
for directory in migration_dirs:
|
||||
migration = Migrations(type=directory.stem)
|
||||
@@ -36,11 +37,9 @@ 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)
|
||||
file_path = app_dirs.MIGRATION_DIR.joinpath(type, file_name)
|
||||
if type == "nextcloud":
|
||||
return nextcloud_migrate(session, file_path)
|
||||
elif type == "chowdown":
|
||||
@@ -53,7 +52,7 @@ def import_nextcloud_directory(
|
||||
def delete_migration_data(type: str, file_name: str):
|
||||
""" Removes migration data from the file system """
|
||||
|
||||
remove_path = MIGRATION_DIR.joinpath(type, file_name)
|
||||
remove_path = app_dirs.MIGRATION_DIR.joinpath(type, file_name)
|
||||
|
||||
if remove_path.is_file():
|
||||
remove_path.unlink()
|
||||
@@ -68,7 +67,7 @@ def delete_migration_data(type: str, file_name: str):
|
||||
@router.post("/{type}/upload")
|
||||
def upload_nextcloud_zipfile(type: str, archive: UploadFile = File(...)):
|
||||
""" Upload a .zip File to later be imported into Mealie """
|
||||
dir = MIGRATION_DIR.joinpath(type)
|
||||
dir = app_dirs.MIGRATION_DIR.joinpath(type)
|
||||
dir.mkdir(parents=True, exist_ok=True)
|
||||
dest = dir.joinpath(archive.filename)
|
||||
|
||||
|
||||
0
mealie/routes/recipe/__init__.py
Normal file
0
mealie/routes/recipe/__init__.py
Normal file
@@ -1,9 +1,9 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from models.recipe_models import AllRecipeRequest
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
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
|
||||
@@ -75,11 +73,9 @@ def get_all_recipes_post(
|
||||
@router.post("/api/recipes/category")
|
||||
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 = [cat.get("recipes") for cat in in_category]
|
||||
# ! 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 = [cat.recipes for cat in in_category if cat]
|
||||
in_category = [item for sublist in in_category for item in sublist]
|
||||
return in_category
|
||||
|
||||
@@ -87,8 +83,8 @@ def filter_by_category(categories: list, session: Session = Depends(generate_ses
|
||||
@router.post("/api/recipes/tag")
|
||||
async def filter_by_tags(tags: list, session: Session = Depends(generate_session)):
|
||||
""" pass a list of tags and get a list of recipes associated with those tags"""
|
||||
#! This should be refactored into a single database call, but I couldn't figure it out
|
||||
# ! This should be refactored into a single database call, but I couldn't figure it out
|
||||
in_tags = [db.tags.get(session, slugify(tag), limit=1) for tag in tags]
|
||||
in_tags = [tag.get("recipes") for tag in in_tags]
|
||||
in_tags = [tag.recipes for tag in in_tags]
|
||||
in_tags = [item for sublist in in_tags for item in sublist]
|
||||
return in_tags
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from models.category_models import RecipeCategoryResponse
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
from mealie.schema.category import CategoryIn, RecipeCategoryResponse
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from sqlalchemy.orm.session import Session
|
||||
from utils.snackbar import SnackResponse
|
||||
|
||||
from utils.snackbar import SnackResponse
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/categories",
|
||||
@@ -19,18 +18,24 @@ async def get_all_recipe_categories(session: Session = Depends(generate_session)
|
||||
return db.categories.get_all_limit_columns(session, ["slug", "name"])
|
||||
|
||||
|
||||
@router.get("/{category}", response_model=RecipeCategoryResponse)
|
||||
def get_all_recipes_by_category(
|
||||
category: str, session: Session = Depends(generate_session)
|
||||
@router.post("")
|
||||
async def create_recipe_category(
|
||||
category: CategoryIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
||||
):
|
||||
""" Creates a Category in the database """
|
||||
|
||||
return db.categories.create(session, category.dict())
|
||||
|
||||
|
||||
@router.get("/{category}", response_model=RecipeCategoryResponse)
|
||||
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)
|
||||
category: str, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
||||
):
|
||||
"""Removes a recipe category from the database. Deleting a
|
||||
category does not impact a recipe. The category will be removed
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from 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 models.recipe_models import RecipeURLIn
|
||||
from services.image_services import read_image, write_image
|
||||
from services.recipe_services import Recipe
|
||||
from services.scraper.scraper import create_from_url
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
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
|
||||
from utils.snackbar import SnackResponse
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/recipes",
|
||||
@@ -16,53 +16,64 @@ router = APIRouter(
|
||||
|
||||
|
||||
@router.post("/create", status_code=201, response_model=str)
|
||||
def create_from_json(data: Recipe, db: Session = Depends(generate_session)) -> str:
|
||||
def create_from_json(
|
||||
data: Recipe,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> str:
|
||||
""" Takes in a JSON string and loads data into the database as a new entry"""
|
||||
new_recipe_slug = data.save_to_db(db)
|
||||
recipe: Recipe = db.recipes.create(session, data.dict())
|
||||
|
||||
return new_recipe_slug
|
||||
return recipe.slug
|
||||
|
||||
|
||||
@router.post("/create-url", status_code=201, response_model=str)
|
||||
def parse_recipe_url(url: RecipeURLIn, db: Session = Depends(generate_session)):
|
||||
def parse_recipe_url(
|
||||
url: RecipeURLIn,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
""" Takes in a URL and attempts to scrape data and load it into the database """
|
||||
|
||||
recipe = create_from_url(url.url)
|
||||
|
||||
recipe.save_to_db(db)
|
||||
recipe: Recipe = db.recipes.create(session, recipe.dict())
|
||||
|
||||
return recipe.slug
|
||||
|
||||
|
||||
@router.get("/{recipe_slug}", response_model=Recipe)
|
||||
def get_recipe(recipe_slug: str, db: Session = Depends(generate_session)):
|
||||
def get_recipe(recipe_slug: str, session: Session = Depends(generate_session)):
|
||||
""" Takes in a recipe slug, returns all data for a recipe """
|
||||
recipe = Recipe.get_by_slug(db, recipe_slug)
|
||||
|
||||
return recipe
|
||||
return db.recipes.get(session, recipe_slug)
|
||||
|
||||
|
||||
@router.put("/{recipe_slug}")
|
||||
def update_recipe(
|
||||
recipe_slug: str, data: Recipe, db: Session = Depends(generate_session)
|
||||
recipe_slug: str,
|
||||
data: Recipe,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
""" Updates a recipe by existing slug and data. """
|
||||
|
||||
new_slug = data.update(db, recipe_slug)
|
||||
recipe: Recipe = db.recipes.update(session, recipe_slug, data.dict())
|
||||
|
||||
return new_slug
|
||||
return recipe.slug
|
||||
|
||||
|
||||
@router.delete("/{recipe_slug}")
|
||||
def delete_recipe(recipe_slug: str, db: Session = Depends(generate_session)):
|
||||
def delete_recipe(
|
||||
recipe_slug: str,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
""" Deletes a recipe by slug """
|
||||
|
||||
try:
|
||||
Recipe.delete(db, recipe_slug)
|
||||
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")
|
||||
|
||||
@@ -83,9 +94,10 @@ def update_recipe_image(
|
||||
image: bytes = File(...),
|
||||
extension: str = Form(...),
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
""" Removes an existing image and replaces it with the incoming file. """
|
||||
response = write_image(recipe_slug, image, extension)
|
||||
Recipe.update_image(session, recipe_slug, extension)
|
||||
db.recipes.update_image(session, recipe_slug, extension)
|
||||
|
||||
return response
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from sqlalchemy.orm.session import Session
|
||||
from utils.snackbar import SnackResponse
|
||||
|
||||
from utils.snackbar import SnackResponse
|
||||
|
||||
router = APIRouter(tags=["Recipes"])
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/recipes/tags",
|
||||
prefix="/api/tags",
|
||||
tags=["Recipe Tags"],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/")
|
||||
@router.get("")
|
||||
async def get_all_recipe_tags(session: Session = Depends(generate_session)):
|
||||
""" Returns a list of available tags in the database """
|
||||
return db.tags.get_all_primary_keys(session)
|
||||
return db.tags.get_all_limit_columns(session, ["slug", "name"])
|
||||
|
||||
|
||||
@router.get("/{tag}")
|
||||
@@ -27,7 +26,9 @@ def get_all_recipes_by_tag(tag: str, session: Session = Depends(generate_session
|
||||
|
||||
|
||||
@router.delete("/{tag}")
|
||||
async def delete_recipe_tag(tag: str, session: Session = Depends(generate_session)):
|
||||
async def delete_recipe_tag(
|
||||
tag: str, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
||||
):
|
||||
"""Removes a recipe tag from the database. Deleting a
|
||||
tag does not impact a recipe. The tag will be removed
|
||||
from any recipes that contain it"""
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from models.settings_models import SiteSettings
|
||||
from services.settings_services import default_settings_init
|
||||
from sqlalchemy.orm.session import Session
|
||||
from utils.post_webhooks import post_webhooks
|
||||
from utils.snackbar import SnackResponse
|
||||
|
||||
router = APIRouter(prefix="/api/site-settings", tags=["Settings"])
|
||||
|
||||
|
||||
@router.get("")
|
||||
def get_main_settings(session: Session = Depends(generate_session)):
|
||||
""" Returns basic site settings """
|
||||
|
||||
try:
|
||||
data = db.settings.get(session, "main")
|
||||
except:
|
||||
default_settings_init(session)
|
||||
data = db.settings.get(session, "main")
|
||||
return data
|
||||
|
||||
|
||||
@router.put("")
|
||||
def update_settings(data: SiteSettings, session: Session = Depends(generate_session)):
|
||||
""" Returns Site Settings """
|
||||
db.settings.update(session, "main", data.dict())
|
||||
|
||||
return SnackResponse.success("Settings Updated")
|
||||
|
||||
|
||||
@router.post("/webhooks/test")
|
||||
def test_webhooks():
|
||||
""" Run the function to test your webhooks """
|
||||
|
||||
return post_webhooks()
|
||||
0
mealie/routes/site_settings/__init__.py
Normal file
0
mealie/routes/site_settings/__init__.py
Normal file
7
mealie/routes/site_settings/all_settings.py
Normal file
7
mealie/routes/site_settings/all_settings.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from fastapi import APIRouter
|
||||
from mealie.routes.site_settings import custom_pages, site_settings
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(custom_pages.router)
|
||||
router.include_router(site_settings.router)
|
||||
80
mealie/routes/site_settings/custom_pages.py
Normal file
80
mealie/routes/site_settings/custom_pages.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from typing import Union
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
from mealie.schema.settings import CustomPageBase, CustomPageOut
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from mealie.schema.user import UserInDB
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
router = APIRouter(prefix="/api/site-settings/custom-pages", tags=["Settings"])
|
||||
|
||||
|
||||
@router.get("")
|
||||
def get_custom_pages(session: Session = Depends(generate_session)):
|
||||
""" Returns the sites custom pages """
|
||||
|
||||
return db.custom_pages.get_all(session)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_new_page(
|
||||
new_page: CustomPageBase,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
):
|
||||
""" Creates a new Custom Page """
|
||||
|
||||
db.custom_pages.create(session, new_page.dict())
|
||||
|
||||
return SnackResponse.success("New Page Created")
|
||||
|
||||
|
||||
@router.put("")
|
||||
async def update_multiple_pages(
|
||||
pages: list[CustomPageOut],
|
||||
session: Session = Depends(generate_session),
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
):
|
||||
""" Update multiple custom pages """
|
||||
for page in pages:
|
||||
db.custom_pages.update(session, page.id, page.dict())
|
||||
return SnackResponse.success("Pages Updated")
|
||||
|
||||
|
||||
@router.get("/{id}")
|
||||
async def get_single_page(
|
||||
id: Union[int, str],
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Removes a custom page from the database """
|
||||
if isinstance(id, int):
|
||||
return db.custom_pages.get(session, id)
|
||||
elif isinstance(id, str):
|
||||
return db.custom_pages.get(session, id, "slug")
|
||||
|
||||
|
||||
@router.put("/{id}")
|
||||
async def update_single_age(
|
||||
data: CustomPageOut,
|
||||
id: int,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
""" Removes a custom page from the database """
|
||||
|
||||
return db.custom_pages.update(session, id, data.dict())
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_custom_page(
|
||||
id: int,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
):
|
||||
""" Removes a custom page from the database """
|
||||
|
||||
db.custom_pages.delete(session, id)
|
||||
return
|
||||
41
mealie/routes/site_settings/site_settings.py
Normal file
41
mealie/routes/site_settings/site_settings.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
from mealie.schema.settings import SiteSettings
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from mealie.schema.user import GroupInDB, UserInDB
|
||||
from mealie.utils.post_webhooks import post_webhooks
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
router = APIRouter(prefix="/api/site-settings", tags=["Settings"])
|
||||
|
||||
|
||||
@router.get("")
|
||||
def get_main_settings(session: Session = Depends(generate_session)):
|
||||
""" Returns basic site settings """
|
||||
|
||||
return db.settings.get(session, 1)
|
||||
|
||||
|
||||
@router.put("")
|
||||
def update_settings(
|
||||
data: SiteSettings,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
""" Returns Site Settings """
|
||||
db.settings.update(session, 1, data.dict())
|
||||
|
||||
return SnackResponse.success("Settings Updated")
|
||||
|
||||
|
||||
@router.post("/webhooks/test")
|
||||
def test_webhooks(
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Run the function to test your webhooks """
|
||||
group_entry: GroupInDB = db.groups.get(session, current_user.group, "name")
|
||||
|
||||
return post_webhooks(group_entry.id, session)
|
||||
@@ -1,9 +1,10 @@
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from models.theme_models import SiteTheme
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from mealie.schema.theme import SiteTheme
|
||||
from sqlalchemy.orm.session import Session
|
||||
from utils.snackbar import SnackResponse
|
||||
from db.database import db
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["Themes"])
|
||||
|
||||
@@ -16,7 +17,7 @@ def get_all_themes(session: Session = Depends(generate_session)):
|
||||
|
||||
|
||||
@router.post("/themes/create")
|
||||
def create_theme(data: SiteTheme, session: Session = Depends(generate_session)):
|
||||
def create_theme(data: SiteTheme, session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
|
||||
""" Creates a site color theme database entry """
|
||||
db.themes.create(session, data.dict())
|
||||
|
||||
@@ -31,7 +32,10 @@ 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)
|
||||
theme_name: str,
|
||||
data: SiteTheme,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
""" Update a theme database entry """
|
||||
db.themes.update(session, theme_name, data.dict())
|
||||
@@ -40,7 +44,7 @@ def update_theme(
|
||||
|
||||
|
||||
@router.delete("/themes/{theme_name}")
|
||||
def delete_theme(theme_name: str, session: Session = Depends(generate_session)):
|
||||
def delete_theme(theme_name: str, session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
|
||||
""" Deletes theme from the database """
|
||||
db.themes.delete(session, theme_name)
|
||||
|
||||
|
||||
0
mealie/routes/users/__init__.py
Normal file
0
mealie/routes/users/__init__.py
Normal file
46
mealie/routes/users/auth.py
Normal file
46
mealie/routes/users/auth.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from mealie.core import security
|
||||
from mealie.core.security import authenticate_user
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_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"])
|
||||
|
||||
|
||||
@router.post("/token/long")
|
||||
@router.post("/token")
|
||||
def get_token(
|
||||
data: OAuth2PasswordRequestForm = Depends(),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
email = data.username
|
||||
password = data.password
|
||||
|
||||
user = authenticate_user(session, email, password)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
access_token = security.create_access_token(dict(sub=email), timedelta(hours=2))
|
||||
return SnackResponse.success(
|
||||
"User Successfully Logged In",
|
||||
{"access_token": access_token, "token_type": "bearer"},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/refresh")
|
||||
async def refresh_token(current_user: UserInDB = Depends(get_current_user)):
|
||||
""" Use a valid token to get another token"""
|
||||
access_token = security.create_access_token(data=dict(sub=current_user.email), expires_delta=timedelta(hours=1))
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
163
mealie/routes/users/crud.py
Normal file
163
mealie/routes/users/crud.py
Normal file
@@ -0,0 +1,163 @@
|
||||
import shutil
|
||||
from datetime import timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends, File, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
from mealie.core import security
|
||||
from mealie.core.config import settings, app_dirs
|
||||
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 mealie.routes.deps import get_current_user
|
||||
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"])
|
||||
|
||||
|
||||
@router.post("", response_model=UserOut, status_code=201)
|
||||
async def create_user(
|
||||
new_user: UserIn,
|
||||
current_user=Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
|
||||
new_user.password = get_password_hash(new_user.password)
|
||||
|
||||
data = db.users.create(session, new_user.dict())
|
||||
return SnackResponse.success(f"User Created: {new_user.full_name}", data)
|
||||
|
||||
|
||||
@router.get("", response_model=list[UserOut])
|
||||
async def get_all_users(
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
|
||||
if current_user.admin:
|
||||
return db.users.get_all(session)
|
||||
else:
|
||||
return {"details": "user not authorized"}
|
||||
|
||||
|
||||
@router.get("/self", response_model=UserOut)
|
||||
async def get_logged_in_user(
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
return current_user.dict()
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=UserOut)
|
||||
async def get_user_by_id(
|
||||
id: int,
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
return db.users.get(session, id)
|
||||
|
||||
|
||||
@router.put("/{id}/reset-password")
|
||||
async def reset_user_password(
|
||||
id: int,
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
|
||||
new_password = get_password_hash(settings.DEFAULT_PASSWORD)
|
||||
db.users.update_password(session, id, new_password)
|
||||
|
||||
return SnackResponse.success("Users Password Reset")
|
||||
|
||||
|
||||
@router.put("/{id}")
|
||||
async def update_user(
|
||||
id: int,
|
||||
new_data: UserBase,
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
|
||||
token = None
|
||||
if current_user.id == id or current_user.admin:
|
||||
db.users.update(session, id, new_data.dict())
|
||||
if current_user.id == id:
|
||||
access_token = security.create_access_token(data=dict(sub=new_data.email), expires_delta=timedelta(hours=2))
|
||||
token = {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
return SnackResponse.success("User Updated", token)
|
||||
|
||||
|
||||
@router.get("/{id}/image")
|
||||
async def get_user_image(id: str):
|
||||
""" Returns a users profile picture """
|
||||
user_dir = app_dirs.USER_DIR.joinpath(id)
|
||||
for recipe_image in user_dir.glob("profile_image.*"):
|
||||
return FileResponse(recipe_image)
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
@router.post("/{id}/image")
|
||||
async def update_user_image(
|
||||
id: str,
|
||||
profile_image: UploadFile = File(...),
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
):
|
||||
""" Updates a User Image """
|
||||
|
||||
extension = profile_image.filename.split(".")[-1]
|
||||
|
||||
app_dirs.USER_DIR.joinpath(id).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
try:
|
||||
[x.unlink() for x in app_dirs.USER_DIR.join(id).glob("profile_image.*")]
|
||||
except:
|
||||
pass
|
||||
|
||||
dest = app_dirs.USER_DIR.joinpath(id, f"profile_image.{extension}")
|
||||
|
||||
with dest.open("wb") as buffer:
|
||||
shutil.copyfileobj(profile_image.file, buffer)
|
||||
|
||||
if dest.is_file:
|
||||
return SnackResponse.success("File uploaded")
|
||||
else:
|
||||
return SnackResponse.error("Failure uploading file")
|
||||
|
||||
|
||||
@router.put("/{id}/password")
|
||||
async def update_password(
|
||||
id: int,
|
||||
password_change: ChangePassword,
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Resets the 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:
|
||||
new_password = get_password_hash(password_change.new_password)
|
||||
db.users.update_password(session, id, new_password)
|
||||
return SnackResponse.success("Password Updated")
|
||||
else:
|
||||
return SnackResponse.error("Existing password does not match")
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_user(
|
||||
id: int,
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Removes a user from the database. Must be the current user or a super user"""
|
||||
|
||||
if id == 1:
|
||||
return SnackResponse.error("Error! Cannot Delete Super User")
|
||||
|
||||
if current_user.id == id or current_user.admin:
|
||||
db.users.delete(session, id)
|
||||
return SnackResponse.error("User Deleted")
|
||||
86
mealie/routes/users/sign_up.py
Normal file
86
mealie/routes/users/sign_up.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import uuid
|
||||
|
||||
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 mealie.routes.deps import get_current_user
|
||||
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"])
|
||||
|
||||
|
||||
@router.get("", response_model=list[SignUpOut])
|
||||
async def get_all_open_sign_ups(
|
||||
current_user=Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Returns a list of open sign up links """
|
||||
|
||||
all_sign_ups = db.sign_ups.get_all(session)
|
||||
|
||||
return all_sign_ups
|
||||
|
||||
|
||||
@router.post("", response_model=SignUpToken)
|
||||
async def create_user_sign_up_key(
|
||||
key_data: SignUpIn,
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Generates a Random Token that a new user can sign up with """
|
||||
|
||||
if current_user.admin:
|
||||
sign_up = {
|
||||
"token": str(uuid.uuid1().hex),
|
||||
"name": key_data.name,
|
||||
"admin": key_data.admin,
|
||||
}
|
||||
db_entry = db.sign_ups.create(session, sign_up)
|
||||
|
||||
return db_entry
|
||||
|
||||
else:
|
||||
return {"details": "not authorized"}
|
||||
|
||||
|
||||
@router.post("/{token}")
|
||||
async def create_user_with_token(
|
||||
token: str,
|
||||
new_user: UserIn,
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Creates a user with a valid sign up token """
|
||||
|
||||
# Validate Token
|
||||
db_entry: SignUpOut = db.sign_ups.get(session, token, limit=1)
|
||||
if not db_entry:
|
||||
return SnackResponse.error("Invalid Token")
|
||||
|
||||
# Create User
|
||||
new_user.admin = db_entry.admin
|
||||
new_user.password = get_password_hash(new_user.password)
|
||||
data = db.users.create(session, new_user.dict())
|
||||
|
||||
# DeleteToken
|
||||
db.sign_ups.delete(session, token)
|
||||
|
||||
# Respond
|
||||
return SnackResponse.success(f"User Created: {new_user.full_name}", data)
|
||||
|
||||
|
||||
@router.delete("/{token}")
|
||||
async def delete_token(
|
||||
token: str,
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Removed a token from the database """
|
||||
if current_user.admin:
|
||||
db.sign_ups.delete(session, token)
|
||||
return SnackResponse.error("Sign Up Token Deleted")
|
||||
else:
|
||||
return {"details", "not authorized"}
|
||||
9
mealie/routes/users/users.py
Normal file
9
mealie/routes/users/users.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from fastapi import APIRouter
|
||||
from mealie.routes.users import auth, crud, sign_up
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(sign_up.router)
|
||||
router.include_router(auth.router)
|
||||
router.include_router(sign_up.router)
|
||||
router.include_router(crud.router)
|
||||
7
mealie/run.sh
Normal file → Executable file
7
mealie/run.sh
Normal file → Executable file
@@ -1,10 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Initialize Database Prerun
|
||||
python mealie/db/init_db.py
|
||||
|
||||
## Migrations
|
||||
# TODO
|
||||
|
||||
## Web Server
|
||||
caddy start --config ./Caddyfile
|
||||
|
||||
## Start API
|
||||
uvicorn app:app --host 0.0.0.0 --port 9000
|
||||
# Start API
|
||||
uvicorn mealie.app:app --host 0.0.0.0 --port 9000
|
||||
0
mealie/schema/__init__.py
Normal file
0
mealie/schema/__init__.py
Normal file
11
mealie/schema/auth.py
Normal file
11
mealie/schema/auth.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
username: Optional[str] = None
|
||||
@@ -7,7 +7,10 @@ from pydantic import BaseModel
|
||||
class BackupOptions(BaseModel):
|
||||
recipes: bool = True
|
||||
settings: bool = True
|
||||
pages: bool = True
|
||||
themes: bool = True
|
||||
groups: bool = True
|
||||
users: bool = True
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
@@ -15,6 +18,26 @@ class BackupOptions(BaseModel):
|
||||
"recipes": True,
|
||||
"settings": True,
|
||||
"themes": True,
|
||||
"groups": True,
|
||||
"users": True,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ImportJob(BackupOptions):
|
||||
name: str
|
||||
force: bool = False
|
||||
rebase: bool = False
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"name": "my_local_backup.zip",
|
||||
"recipes": True,
|
||||
"settings": True,
|
||||
"themes": True,
|
||||
"groups": True,
|
||||
"users": True,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,24 +78,3 @@ class Imports(BaseModel):
|
||||
"templates": ["recipes.md", "custom_template.md"],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ImportJob(BaseModel):
|
||||
name: str
|
||||
recipes: bool
|
||||
force: bool = False
|
||||
rebase: bool = False
|
||||
themes: bool = False
|
||||
settings: bool = False
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"name": "my_local_backup.zip",
|
||||
"recipes": True,
|
||||
"force": False,
|
||||
"rebase": False,
|
||||
"themes": False,
|
||||
"settings": False,
|
||||
}
|
||||
}
|
||||
31
mealie/schema/category.py
Normal file
31
mealie/schema/category.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from mealie.schema.recipe import Recipe
|
||||
|
||||
|
||||
class CategoryIn(CamelModel):
|
||||
name: str
|
||||
|
||||
|
||||
class CategoryBase(CategoryIn):
|
||||
id: int
|
||||
slug: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class RecipeCategoryResponse(CategoryBase):
|
||||
recipes: Optional[List[Recipe]]
|
||||
|
||||
class Config:
|
||||
schema_extra = {"example": {"id": 1, "name": "dinner", "recipes": [{}]}}
|
||||
|
||||
|
||||
class TagBase(CategoryBase):
|
||||
pass
|
||||
|
||||
|
||||
class RecipeTagResponse(TagBase):
|
||||
pass
|
||||
51
mealie/schema/meal.py
Normal file
51
mealie/schema/meal.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from datetime import date
|
||||
from typing import List, Optional
|
||||
|
||||
from mealie.db.models.mealplan import MealPlanModel
|
||||
from pydantic import BaseModel, validator
|
||||
from pydantic.utils import GetterDict
|
||||
|
||||
|
||||
class MealIn(BaseModel):
|
||||
name: Optional[str]
|
||||
slug: Optional[str]
|
||||
date: Optional[date]
|
||||
|
||||
|
||||
class MealOut(MealIn):
|
||||
image: Optional[str]
|
||||
description: Optional[str]
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class MealPlanIn(BaseModel):
|
||||
group: str
|
||||
startDate: date
|
||||
endDate: date
|
||||
meals: List[MealIn]
|
||||
|
||||
@validator("endDate")
|
||||
def endDate_after_startDate(v, values, config, field):
|
||||
if "startDate" in values and v < values["startDate"]:
|
||||
raise ValueError("EndDate should be greater than StartDate")
|
||||
return v
|
||||
|
||||
|
||||
class MealPlanProcessed(MealPlanIn):
|
||||
meals: list[MealOut]
|
||||
|
||||
|
||||
class MealPlanInDB(MealPlanProcessed):
|
||||
uid: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@classmethod
|
||||
def getter_dict(_cls, name_orm: MealPlanModel):
|
||||
return {
|
||||
**GetterDict(name_orm),
|
||||
"group": name_orm.group.name,
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import datetime
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
from pydantic import BaseModel, validator
|
||||
from pydantic.utils import GetterDict
|
||||
from slugify import slugify
|
||||
|
||||
|
||||
@@ -9,19 +11,39 @@ class RecipeNote(BaseModel):
|
||||
title: str
|
||||
text: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class RecipeStep(BaseModel):
|
||||
text: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class Nutrition(BaseModel):
|
||||
calories: Optional[str]
|
||||
fatContent: Optional[str]
|
||||
fiberContent: Optional[str]
|
||||
proteinContent: Optional[str]
|
||||
sodiumContent: Optional[str]
|
||||
sugarContent: Optional[str]
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class Recipe(BaseModel):
|
||||
# Standard Schema
|
||||
name: str
|
||||
description: Optional[str]
|
||||
image: Optional[Any]
|
||||
recipeYield: Optional[str]
|
||||
recipeIngredient: Optional[list]
|
||||
recipeInstructions: Optional[list]
|
||||
recipeCategory: Optional[List[str]] = []
|
||||
recipeIngredient: Optional[list[str]]
|
||||
recipeInstructions: Optional[list[RecipeStep]]
|
||||
nutrition: Optional[Nutrition]
|
||||
tools: Optional[list[str]] = []
|
||||
|
||||
totalTime: Optional[str] = None
|
||||
prepTime: Optional[str] = None
|
||||
@@ -29,7 +51,6 @@ class Recipe(BaseModel):
|
||||
|
||||
# Mealie Specific
|
||||
slug: Optional[str] = ""
|
||||
categories: Optional[List[str]] = []
|
||||
tags: Optional[List[str]] = []
|
||||
dateAdded: Optional[datetime.date]
|
||||
notes: Optional[List[RecipeNote]] = []
|
||||
@@ -38,6 +59,19 @@ class Recipe(BaseModel):
|
||||
extras: Optional[dict] = {}
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@classmethod
|
||||
def getter_dict(_cls, name_orm: RecipeModel):
|
||||
return {
|
||||
**GetterDict(name_orm),
|
||||
"recipeIngredient": [x.ingredient for x in name_orm.recipeIngredient],
|
||||
"recipeCategory": [x.name for x in name_orm.recipeCategory],
|
||||
"tags": [x.name for x in name_orm.tags],
|
||||
"tools": [x.tool for x in name_orm.tools],
|
||||
"extras": {x.key_name: x.value for x in name_orm.extras},
|
||||
}
|
||||
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"name": "Chicken and Rice With Leeks and Salsa Verde",
|
||||
@@ -56,7 +90,7 @@ class Recipe(BaseModel):
|
||||
],
|
||||
"slug": "chicken-and-rice-with-leeks-and-salsa-verde",
|
||||
"tags": ["favorite", "yummy!"],
|
||||
"categories": ["Dinner", "Pasta"],
|
||||
"recipeCategory": ["Dinner", "Pasta"],
|
||||
"notes": [{"title": "Watch Out!", "text": "Prep the day before!"}],
|
||||
"orgURL": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde",
|
||||
"rating": 3,
|
||||
@@ -69,11 +103,10 @@ class Recipe(BaseModel):
|
||||
name: str = values["name"]
|
||||
calc_slug: str = slugify(name)
|
||||
|
||||
if slug == calc_slug:
|
||||
return slug
|
||||
else:
|
||||
if slug != calc_slug:
|
||||
slug = calc_slug
|
||||
return slug
|
||||
|
||||
return slug
|
||||
|
||||
|
||||
class AllRecipeRequest(BaseModel):
|
||||
33
mealie/schema/restore.py
Normal file
33
mealie/schema/restore.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic.main import BaseModel
|
||||
|
||||
|
||||
class ImportBase(BaseModel):
|
||||
name: str
|
||||
status: bool
|
||||
exception: Optional[str]
|
||||
|
||||
|
||||
class RecipeImport(ImportBase):
|
||||
slug: Optional[str]
|
||||
|
||||
|
||||
class ThemeImport(ImportBase):
|
||||
pass
|
||||
|
||||
|
||||
class SettingsImport(ImportBase):
|
||||
pass
|
||||
|
||||
|
||||
class GroupImport(ImportBase):
|
||||
pass
|
||||
|
||||
|
||||
class UserImport(ImportBase):
|
||||
pass
|
||||
|
||||
|
||||
class CustomPageImport(ImportBase):
|
||||
pass
|
||||
55
mealie/schema/settings.py
Normal file
55
mealie/schema/settings.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from mealie.schema.category import CategoryBase
|
||||
from pydantic import validator
|
||||
from slugify import slugify
|
||||
|
||||
|
||||
class SiteSettings(CamelModel):
|
||||
language: str = "en"
|
||||
show_recent: bool = True
|
||||
cards_per_section: int = 9
|
||||
categories: Optional[list[CategoryBase]] = []
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"language": "en",
|
||||
"showRecent": True,
|
||||
"categories": [
|
||||
{"id": 1, "name": "thanksgiving", "slug": "thanksgiving"},
|
||||
{"id": 2, "name": "homechef", "slug": "homechef"},
|
||||
{"id": 3, "name": "potatoes", "slug": "potatoes"},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CustomPageBase(CamelModel):
|
||||
name: str
|
||||
slug: Optional[str]
|
||||
position: int
|
||||
categories: list[CategoryBase] = []
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@validator("slug", always=True, pre=True)
|
||||
def validate_slug(slug: str, values):
|
||||
name: str = values["name"]
|
||||
calc_slug: str = slugify(name)
|
||||
|
||||
if slug != calc_slug:
|
||||
slug = calc_slug
|
||||
|
||||
return slug
|
||||
|
||||
|
||||
class CustomPageOut(CustomPageBase):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
17
mealie/schema/sign_up.py
Normal file
17
mealie/schema/sign_up.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from fastapi_camelcase import CamelModel
|
||||
|
||||
|
||||
class SignUpIn(CamelModel):
|
||||
name: str
|
||||
admin: bool
|
||||
|
||||
|
||||
class SignUpToken(SignUpIn):
|
||||
token: str
|
||||
|
||||
|
||||
class SignUpOut(SignUpToken):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
@@ -1,20 +1,25 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Colors(BaseModel):
|
||||
primary: str
|
||||
accent: str
|
||||
secondary: str
|
||||
success: str
|
||||
info: str
|
||||
warning: str
|
||||
error: str
|
||||
primary: str = "#E58325"
|
||||
accent: str = "#00457A"
|
||||
secondary: str = "#973542"
|
||||
success: str = "#4CAF50"
|
||||
info: str = "#4990BA"
|
||||
warning: str = "#FF4081"
|
||||
error: str = "#EF5350"
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class SiteTheme(BaseModel):
|
||||
name: str
|
||||
colors: Colors
|
||||
name: str = "default"
|
||||
colors: Colors = Colors()
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"name": "default",
|
||||
@@ -28,4 +33,4 @@ class SiteTheme(BaseModel):
|
||||
"error": "#EF5350",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
97
mealie/schema/user.py
Normal file
97
mealie/schema/user.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from mealie.core.config import settings
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.users import User
|
||||
from mealie.schema.category import CategoryBase
|
||||
from mealie.schema.meal import MealPlanInDB
|
||||
from pydantic.utils import GetterDict
|
||||
|
||||
|
||||
class ChangePassword(CamelModel):
|
||||
current_password: str
|
||||
new_password: str
|
||||
|
||||
|
||||
class GroupBase(CamelModel):
|
||||
name: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class UserBase(CamelModel):
|
||||
full_name: Optional[str] = None
|
||||
email: str
|
||||
admin: bool
|
||||
group: Optional[str]
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@classmethod
|
||||
def getter_dict(_cls, name_orm: User):
|
||||
return {
|
||||
**GetterDict(name_orm),
|
||||
"group": name_orm.group.name,
|
||||
}
|
||||
|
||||
schema_extra = {
|
||||
"fullName": "Change Me",
|
||||
"email": "changeme@email.com",
|
||||
"group": settings.DEFAULT_GROUP,
|
||||
"admin": "false",
|
||||
}
|
||||
|
||||
|
||||
class UserIn(UserBase):
|
||||
password: str
|
||||
|
||||
|
||||
class UserOut(UserBase):
|
||||
id: int
|
||||
group: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@classmethod
|
||||
def getter_dict(cls, ormModel: User):
|
||||
return {
|
||||
**GetterDict(ormModel),
|
||||
"group": ormModel.group.name,
|
||||
}
|
||||
|
||||
|
||||
class UserInDB(UserOut):
|
||||
password: str
|
||||
pass
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class UpdateGroup(GroupBase):
|
||||
id: int
|
||||
name: str
|
||||
categories: Optional[list[CategoryBase]] = []
|
||||
|
||||
webhook_urls: list[str] = []
|
||||
webhook_time: str = "00:00"
|
||||
webhook_enable: bool
|
||||
|
||||
|
||||
class GroupInDB(UpdateGroup):
|
||||
users: Optional[list[UserOut]]
|
||||
mealplans: Optional[list[MealPlanInDB]]
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@classmethod
|
||||
def getter_dict(_cls, orm_model: Group):
|
||||
return {
|
||||
**GetterDict(orm_model),
|
||||
"webhook_urls": [x.url for x in orm_model.webhook_urls if x],
|
||||
}
|
||||
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
@@ -2,20 +2,21 @@ import json
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
from app_config import BACKUP_DIR, IMG_DIR, TEMP_DIR, TEMPLATE_DIR
|
||||
from db.database import db
|
||||
from db.db_setup import create_session
|
||||
from fastapi.logger import logger
|
||||
from jinja2 import Template
|
||||
from services.meal_services import MealPlan
|
||||
from services.recipe_services import Recipe
|
||||
from utils.logger import logger
|
||||
from mealie.core.config import app_dirs
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session
|
||||
from pydantic.main import BaseModel
|
||||
|
||||
|
||||
class ExportDatabase:
|
||||
def __init__(self, session, tag=None, templates=None) -> None:
|
||||
def __init__(self, tag=None, templates=None) -> None:
|
||||
"""Export a Mealie database. Export interacts directly with class objects and can be used
|
||||
with any supported backend database platform. By default tags are timestands, and no Jinja2 templates are rendered
|
||||
with any supported backend database platform. By default tags are timestamps, and no
|
||||
Jinja2 templates are rendered
|
||||
|
||||
|
||||
Args:
|
||||
@@ -27,17 +28,12 @@ class ExportDatabase:
|
||||
else:
|
||||
export_tag = datetime.now().strftime("%Y-%b-%d")
|
||||
|
||||
self.session = session
|
||||
self.main_dir = TEMP_DIR.joinpath(export_tag)
|
||||
self.main_dir = app_dirs.TEMP_DIR.joinpath(export_tag)
|
||||
self.img_dir = self.main_dir.joinpath("images")
|
||||
self.recipe_dir = self.main_dir.joinpath("recipes")
|
||||
self.themes_dir = self.main_dir.joinpath("themes")
|
||||
self.settings_dir = self.main_dir.joinpath("settings")
|
||||
self.templates_dir = self.main_dir.joinpath("templates")
|
||||
self.mealplans_dir = self.main_dir.joinpath("mealplans")
|
||||
|
||||
try:
|
||||
self.templates = [TEMPLATE_DIR.joinpath(x) for x in templates]
|
||||
self.templates = [app_dirs.TEMPLATE_DIR.joinpath(x) for x in templates]
|
||||
except:
|
||||
self.templates = False
|
||||
logger.info("No Jinja2 Templates Registered for Export")
|
||||
@@ -45,80 +41,56 @@ class ExportDatabase:
|
||||
required_dirs = [
|
||||
self.main_dir,
|
||||
self.img_dir,
|
||||
self.recipe_dir,
|
||||
self.themes_dir,
|
||||
self.settings_dir,
|
||||
self.templates_dir,
|
||||
self.mealplans_dir,
|
||||
]
|
||||
|
||||
for dir in required_dirs:
|
||||
dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def export_recipes(self):
|
||||
all_recipes = Recipe.get_all(self.session)
|
||||
|
||||
for recipe in all_recipes:
|
||||
logger.info(f"Backing Up Recipes: {recipe}")
|
||||
|
||||
filename = recipe.get("slug") + ".json"
|
||||
file_path = self.recipe_dir.joinpath(filename)
|
||||
|
||||
ExportDatabase._write_json_file(recipe, file_path)
|
||||
|
||||
if self.templates:
|
||||
self._export_template(recipe)
|
||||
|
||||
def _export_template(self, recipe_data: dict):
|
||||
def export_templates(self, recipe_list: list[BaseModel]):
|
||||
for template_path in self.templates:
|
||||
out_dir = self.templates_dir.joinpath(template_path.name)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(template_path, "r") as f:
|
||||
template = Template(f.read())
|
||||
|
||||
filename = recipe_data.get("name") + template_path.suffix
|
||||
out_file = self.templates_dir.joinpath(filename)
|
||||
for recipe in recipe_list:
|
||||
filename = recipe.slug + template_path.suffix
|
||||
out_file = out_dir.joinpath(filename)
|
||||
|
||||
content = template.render(recipe=recipe_data)
|
||||
content = template.render(recipe=recipe)
|
||||
|
||||
with open(out_file, "w") as f:
|
||||
f.write(content)
|
||||
with open(out_file, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
def export_images(self):
|
||||
for file in IMG_DIR.iterdir():
|
||||
for file in app_dirs.IMG_DIR.iterdir():
|
||||
shutil.copy(file, self.img_dir.joinpath(file.name))
|
||||
|
||||
def export_settings(self):
|
||||
all_settings = db.settings.get(self.session, "main")
|
||||
out_file = self.settings_dir.joinpath("settings.json")
|
||||
ExportDatabase._write_json_file(all_settings, out_file)
|
||||
def export_items(self, items: list[BaseModel], folder_name: str, export_list=True):
|
||||
items = [x.dict() for x in items]
|
||||
out_dir = self.main_dir.joinpath(folder_name)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def export_themes(self):
|
||||
all_themes = db.themes.get_all(self.session)
|
||||
if all_themes:
|
||||
out_file = self.themes_dir.joinpath("themes.json")
|
||||
ExportDatabase._write_json_file(all_themes, out_file)
|
||||
|
||||
def export_meals(self):
|
||||
#! Problem Parseing Datetime Objects... May come back to this
|
||||
meal_plans = MealPlan.get_all(self.session)
|
||||
if meal_plans:
|
||||
meal_plans = [x.dict() for x in meal_plans]
|
||||
|
||||
out_file = self.mealplans_dir.joinpath("mealplans.json")
|
||||
ExportDatabase._write_json_file(meal_plans, out_file)
|
||||
if export_list:
|
||||
ExportDatabase._write_json_file(items, out_dir.joinpath(f"{folder_name}.json"))
|
||||
else:
|
||||
for item in items:
|
||||
ExportDatabase._write_json_file(item, out_dir.joinpath(f"{item.get('name')}.json"))
|
||||
|
||||
@staticmethod
|
||||
def _write_json_file(data: dict, out_file: Path):
|
||||
def _write_json_file(data: Union[dict, list], out_file: Path):
|
||||
json_data = json.dumps(data, indent=4, default=str)
|
||||
|
||||
with open(out_file, "w") as f:
|
||||
f.write(json_data)
|
||||
|
||||
def finish_export(self):
|
||||
zip_path = BACKUP_DIR.joinpath(f"{self.main_dir.name}")
|
||||
zip_path = app_dirs.BACKUP_DIR.joinpath(f"{self.main_dir.name}")
|
||||
shutil.make_archive(zip_path, "zip", self.main_dir)
|
||||
|
||||
shutil.rmtree(TEMP_DIR)
|
||||
shutil.rmtree(app_dirs.TEMP_DIR)
|
||||
|
||||
return str(zip_path.absolute()) + ".zip"
|
||||
|
||||
@@ -129,32 +101,47 @@ def backup_all(
|
||||
templates=None,
|
||||
export_recipes=True,
|
||||
export_settings=True,
|
||||
export_pages=True,
|
||||
export_themes=True,
|
||||
export_users=True,
|
||||
export_groups=True,
|
||||
):
|
||||
db_export = ExportDatabase(session=session, tag=tag, templates=templates)
|
||||
db_export = ExportDatabase(tag=tag, templates=templates)
|
||||
|
||||
if export_users:
|
||||
all_users = db.users.get_all(session)
|
||||
db_export.export_items(all_users, "users")
|
||||
|
||||
if export_groups:
|
||||
all_groups = db.groups.get_all(session)
|
||||
db_export.export_items(all_groups, "groups")
|
||||
|
||||
if export_recipes:
|
||||
db_export.export_recipes()
|
||||
all_recipes = db.recipes.get_all(session)
|
||||
db_export.export_items(all_recipes, "recipes", export_list=False)
|
||||
db_export.export_templates(all_recipes)
|
||||
db_export.export_images()
|
||||
|
||||
if export_settings:
|
||||
db_export.export_settings()
|
||||
all_settings = db.settings.get_all(session)
|
||||
db_export.export_items(all_settings, "settings")
|
||||
|
||||
if export_pages:
|
||||
all_pages = db.custom_pages.get_all(session)
|
||||
db_export.export_items(all_pages, "pages")
|
||||
|
||||
if export_themes:
|
||||
db_export.export_themes()
|
||||
# db_export.export_meals()
|
||||
all_themes = db.themes.get_all(session)
|
||||
db_export.export_items(all_themes, "themes")
|
||||
|
||||
return db_export.finish_export()
|
||||
|
||||
|
||||
def auto_backup_job():
|
||||
for backup in BACKUP_DIR.glob("Auto*.zip"):
|
||||
for backup in app_dirs.BACKUP_DIR.glob("Auto*.zip"):
|
||||
backup.unlink()
|
||||
|
||||
templates = []
|
||||
for template in TEMPLATE_DIR.iterdir():
|
||||
templates.append(template)
|
||||
|
||||
templates = [template for template in app_dirs.TEMPLATE_DIR.iterdir()]
|
||||
session = create_session()
|
||||
backup_all(session=session, tag="Auto", templates=templates)
|
||||
logger.info("Auto Backup Called")
|
||||
|
||||
@@ -2,15 +2,17 @@ import json
|
||||
import shutil
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
from typing import Callable, List
|
||||
|
||||
from app_config import BACKUP_DIR, IMG_DIR, TEMP_DIR
|
||||
from db.database import db
|
||||
from models.import_models import RecipeImport, SettingsImport, ThemeImport
|
||||
from models.theme_models import SiteTheme
|
||||
from services.recipe_services import Recipe
|
||||
from mealie.core.config import app_dirs
|
||||
from mealie.db.database import db
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.schema.restore import CustomPageImport, GroupImport, RecipeImport, SettingsImport, ThemeImport, UserImport
|
||||
from mealie.schema.settings import CustomPageOut, SiteSettings
|
||||
from mealie.schema.theme import SiteTheme
|
||||
from mealie.schema.user import UpdateGroup, UserInDB
|
||||
from pydantic.main import BaseModel
|
||||
from sqlalchemy.orm.session import Session
|
||||
from utils.logger import logger
|
||||
|
||||
|
||||
class ImportDatabase:
|
||||
@@ -18,92 +20,56 @@ class ImportDatabase:
|
||||
self,
|
||||
session: Session,
|
||||
zip_archive: str,
|
||||
import_recipes: bool = True,
|
||||
import_settings: bool = True,
|
||||
import_themes: bool = True,
|
||||
force_import: bool = False,
|
||||
rebase: bool = False,
|
||||
) -> None:
|
||||
"""Import a database.zip file exported from mealie.
|
||||
|
||||
Args:
|
||||
session (Session): SqlAlchemy Session
|
||||
zip_archive (str): The filename contained in the backups directory
|
||||
import_recipes (bool, optional): Import Recipes?. Defaults to True.
|
||||
import_settings (bool, optional): Determines if settings are imported. Defaults to True.
|
||||
import_themes (bool, optional): Determines if themes are imported. Defaults to True.
|
||||
force_import (bool, optional): Force import will update all existing recipes. If False existing recipes are skipped. Defaults to False.
|
||||
rebase (bool, optional): Rebase will first clear the database and then import Recipes. Defaults to False.
|
||||
|
||||
Raises:
|
||||
Exception: If the zip file does not exists an exception raise.
|
||||
"""
|
||||
self.session = session
|
||||
self.archive = BACKUP_DIR.joinpath(zip_archive)
|
||||
self.imp_recipes = import_recipes
|
||||
self.imp_settings = import_settings
|
||||
self.imp_themes = import_themes
|
||||
self.archive = app_dirs.BACKUP_DIR.joinpath(zip_archive)
|
||||
self.force_imports = force_import
|
||||
self.force_rebase = rebase
|
||||
|
||||
if self.archive.is_file():
|
||||
self.import_dir = TEMP_DIR.joinpath("active_import")
|
||||
self.import_dir = app_dirs.TEMP_DIR.joinpath("active_import")
|
||||
self.import_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with zipfile.ZipFile(self.archive, "r") as zip_ref:
|
||||
zip_ref.extractall(self.import_dir)
|
||||
pass
|
||||
else:
|
||||
raise Exception("Import file does not exist")
|
||||
|
||||
def run(self):
|
||||
recipe_report = []
|
||||
settings_report = []
|
||||
theme_report = []
|
||||
if self.imp_recipes:
|
||||
recipe_report = self.import_recipes()
|
||||
if self.imp_settings:
|
||||
settings_report = self.import_settings()
|
||||
if self.imp_themes:
|
||||
theme_report = self.import_themes()
|
||||
|
||||
self.clean_up()
|
||||
|
||||
return {
|
||||
"recipeImports": recipe_report,
|
||||
"settingsReport": settings_report,
|
||||
"themeReport": theme_report,
|
||||
}
|
||||
|
||||
def import_recipes(self):
|
||||
recipe_dir: Path = self.import_dir.joinpath("recipes")
|
||||
|
||||
imports = []
|
||||
successful_imports = []
|
||||
|
||||
for recipe in recipe_dir.glob("*.json"):
|
||||
with open(recipe, "r") as f:
|
||||
recipe_dict = json.loads(f.read())
|
||||
recipe_dict = ImportDatabase._recipe_migration(recipe_dict)
|
||||
try:
|
||||
recipe_obj = Recipe(**recipe_dict)
|
||||
recipe_obj.save_to_db(self.session)
|
||||
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}")
|
||||
recipes = ImportDatabase.read_models_file(
|
||||
file_path=recipe_dir, model=Recipe, single_file=False, migrate=ImportDatabase._recipe_migration
|
||||
)
|
||||
|
||||
except Exception as inst:
|
||||
logger.error(inst)
|
||||
logger.info(f"Failed Import: {recipe.stem}")
|
||||
import_status = RecipeImport(
|
||||
name=recipe.stem,
|
||||
slug=recipe.stem,
|
||||
status=False,
|
||||
exception=str(inst),
|
||||
)
|
||||
imports.append(import_status)
|
||||
for recipe in recipes:
|
||||
recipe: Recipe
|
||||
|
||||
import_status = self.import_model(
|
||||
db_table=db.recipes,
|
||||
model=recipe,
|
||||
return_model=RecipeImport,
|
||||
name_attr="name",
|
||||
search_key="slug",
|
||||
slug=recipe.slug,
|
||||
)
|
||||
|
||||
if import_status.status:
|
||||
successful_imports.append(recipe.slug)
|
||||
|
||||
imports.append(import_status)
|
||||
|
||||
self._import_images(successful_imports)
|
||||
|
||||
@@ -111,6 +77,9 @@ class ImportDatabase:
|
||||
|
||||
@staticmethod
|
||||
def _recipe_migration(recipe_dict: dict) -> dict:
|
||||
if recipe_dict.get("categories", False):
|
||||
recipe_dict["recipeCategory"] = recipe_dict.get("categories")
|
||||
del recipe_dict["categories"]
|
||||
try:
|
||||
del recipe_dict["_id"]
|
||||
del recipe_dict["dateAdded"]
|
||||
@@ -119,17 +88,14 @@ 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 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 cat != ""]
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -142,50 +108,221 @@ class ImportDatabase:
|
||||
image_dir = self.import_dir.joinpath("images")
|
||||
for image in image_dir.iterdir():
|
||||
if image.stem in successful_imports:
|
||||
shutil.copy(image, IMG_DIR)
|
||||
shutil.copy(image, app_dirs.IMG_DIR)
|
||||
|
||||
def import_themes(self):
|
||||
themes_file = self.import_dir.joinpath("themes", "themes.json")
|
||||
themes = ImportDatabase.read_models_file(themes_file, SiteTheme)
|
||||
theme_imports = []
|
||||
with open(themes_file, "r") as f:
|
||||
themes: list[dict] = json.loads(f.read())
|
||||
for theme in themes:
|
||||
if theme.get("name") == "default":
|
||||
continue
|
||||
new_theme = SiteTheme(**theme)
|
||||
try:
|
||||
|
||||
db.themes.create(self.session, new_theme.dict())
|
||||
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))
|
||||
)
|
||||
for theme in themes:
|
||||
if theme.name == "default":
|
||||
continue
|
||||
|
||||
import_status = self.import_model(
|
||||
db_table=db.themes,
|
||||
model=theme,
|
||||
return_model=ThemeImport,
|
||||
name_attr="name",
|
||||
search_key="name",
|
||||
)
|
||||
|
||||
theme_imports.append(import_status)
|
||||
|
||||
return theme_imports
|
||||
|
||||
def import_settings(self):
|
||||
def import_settings(self): # ! Broken
|
||||
settings_file = self.import_dir.joinpath("settings", "settings.json")
|
||||
settings_imports = []
|
||||
settings = ImportDatabase.read_models_file(settings_file, SiteSettings)
|
||||
settings = settings[0]
|
||||
|
||||
with open(settings_file, "r") as f:
|
||||
settings: dict = json.loads(f.read())
|
||||
try:
|
||||
db.settings.update(self.session, 1, settings.dict())
|
||||
import_status = SettingsImport(name="Site Settings", status=True)
|
||||
|
||||
name = settings.get("name")
|
||||
except Exception as inst:
|
||||
self.session.rollback()
|
||||
import_status = SettingsImport(name="Site Settings", status=False, exception=str(inst))
|
||||
|
||||
try:
|
||||
db.settings.update(self.session, name, settings)
|
||||
import_status = SettingsImport(name=name, status=True)
|
||||
return [import_status]
|
||||
|
||||
except Exception as inst:
|
||||
import_status = SettingsImport(
|
||||
name=name, status=False, exception=str(inst)
|
||||
def import_pages(self):
|
||||
pages_file = self.import_dir.joinpath("pages", "pages.json")
|
||||
pages = ImportDatabase.read_models_file(pages_file, CustomPageOut)
|
||||
|
||||
page_imports = []
|
||||
for page in pages:
|
||||
import_stats = self.import_model(
|
||||
db_table=db.custom_pages, model=page, return_model=CustomPageImport, name_attr="name", search_key="slug"
|
||||
)
|
||||
page_imports.append(import_stats)
|
||||
|
||||
return page_imports
|
||||
|
||||
def import_groups(self):
|
||||
groups_file = self.import_dir.joinpath("groups", "groups.json")
|
||||
groups = ImportDatabase.read_models_file(groups_file, UpdateGroup)
|
||||
group_imports = []
|
||||
|
||||
for group in groups:
|
||||
import_status = self.import_model(db.groups, group, GroupImport, search_key="name")
|
||||
group_imports.append(import_status)
|
||||
|
||||
return group_imports
|
||||
|
||||
def import_users(self):
|
||||
users_file = self.import_dir.joinpath("users", "users.json")
|
||||
users = ImportDatabase.read_models_file(users_file, UserInDB)
|
||||
user_imports = []
|
||||
for user in users:
|
||||
if user.id == 1: # Update Default User
|
||||
db.users.update(self.session, 1, user.dict())
|
||||
import_status = UserImport(name=user.full_name, status=True)
|
||||
user_imports.append(import_status)
|
||||
continue
|
||||
|
||||
import_status = self.import_model(
|
||||
db_table=db.users,
|
||||
model=user,
|
||||
return_model=UserImport,
|
||||
name_attr="full_name",
|
||||
search_key="email",
|
||||
)
|
||||
|
||||
user_imports.append(import_status)
|
||||
|
||||
return user_imports
|
||||
|
||||
@staticmethod
|
||||
def read_models_file(file_path: Path, model: BaseModel, single_file=True, migrate: Callable = None):
|
||||
"""A general purpose function that is used to process a backup `.json` file created by mealie
|
||||
note that if the file doesn't not exists the function will return any empty list
|
||||
|
||||
Args:
|
||||
file_path (Path): The path to the .json file or directory
|
||||
model (BaseModel): The pydantic model that will be created from the .json file entries
|
||||
single_file (bool, optional): If true, the json data will be treated as list, if false it will use glob style matches and treat each file as its own entry. Defaults to True.
|
||||
migrate (Callable, optional): A migrate function that will be called on the data prior to creating a model. Defaults to None.
|
||||
|
||||
Returns:
|
||||
[type]: [description]
|
||||
"""
|
||||
if not file_path.exists():
|
||||
return []
|
||||
|
||||
if single_file:
|
||||
with open(file_path, "r") as f:
|
||||
file_data = json.loads(f.read())
|
||||
|
||||
if migrate:
|
||||
file_data = [migrate(x) for x in file_data]
|
||||
|
||||
return [model(**g) for g in file_data]
|
||||
|
||||
all_models = []
|
||||
for file in file_path.glob("*.json"):
|
||||
with open(file, "r") as f:
|
||||
file_data = json.loads(f.read())
|
||||
|
||||
if migrate:
|
||||
file_data = migrate(file_data)
|
||||
|
||||
all_models.append(model(**file_data))
|
||||
|
||||
return all_models
|
||||
|
||||
def import_model(self, db_table, model, return_model, name_attr="name", search_key="id", **kwargs):
|
||||
"""A general purpose function used to insert a list of pydantic modelsi into the database.
|
||||
The assumption at this point is that the models that are inserted. If self.force_imports is true
|
||||
any existing entries will be removed prior to creation
|
||||
|
||||
Args:
|
||||
db_table ([type]): A database table like `db.users`
|
||||
model ([type]): The Pydantic model that matches the database
|
||||
return_model ([type]): The return model that will be used for the 'report'
|
||||
name_attr (str, optional): The name property on the return model. Defaults to "name".
|
||||
search_key (str, optional): The key used to identify if an the entry already exists. Defaults to "id"
|
||||
**kwargs (): Any kwargs passed will be used to set attributes on the `return_model`
|
||||
|
||||
Returns:
|
||||
[type]: Returns the `return_model` specified.
|
||||
"""
|
||||
model_name = getattr(model, name_attr)
|
||||
search_value = getattr(model, search_key)
|
||||
|
||||
item = db_table.get(self.session, search_value, search_key)
|
||||
if item:
|
||||
if not self.force_imports:
|
||||
return return_model(
|
||||
name=model_name,
|
||||
status=False,
|
||||
exception=f"Table entry with matching '{search_key}': '{search_value}' exists",
|
||||
)
|
||||
|
||||
settings_imports.append(import_status)
|
||||
primary_key = getattr(item, db_table.primary_key)
|
||||
db_table.delete(self.session, primary_key)
|
||||
try:
|
||||
db_table.create(self.session, model.dict())
|
||||
import_status = return_model(name=model_name, status=True)
|
||||
|
||||
return settings_imports
|
||||
except Exception as inst:
|
||||
self.session.rollback()
|
||||
import_status = return_model(name=model_name, status=False, exception=str(inst))
|
||||
|
||||
for key, value in kwargs.items():
|
||||
setattr(return_model, key, value)
|
||||
|
||||
return import_status
|
||||
|
||||
def clean_up(self):
|
||||
shutil.rmtree(TEMP_DIR)
|
||||
shutil.rmtree(app_dirs.TEMP_DIR)
|
||||
|
||||
|
||||
def import_database(
|
||||
session: Session,
|
||||
archive,
|
||||
import_recipes=True,
|
||||
import_settings=True,
|
||||
import_pages=True,
|
||||
import_themes=True,
|
||||
import_users=True,
|
||||
import_groups=True,
|
||||
force_import: bool = False,
|
||||
rebase: bool = False,
|
||||
):
|
||||
import_session = ImportDatabase(session, archive, force_import)
|
||||
|
||||
recipe_report = []
|
||||
if import_recipes:
|
||||
recipe_report = import_session.import_recipes()
|
||||
|
||||
settings_report = []
|
||||
if import_settings:
|
||||
settings_report = import_session.import_settings()
|
||||
|
||||
theme_report = []
|
||||
if import_themes:
|
||||
theme_report = import_session.import_themes()
|
||||
|
||||
if import_pages:
|
||||
print("IMport Pages")
|
||||
page_report = import_session.import_pages()
|
||||
|
||||
group_report = []
|
||||
if import_groups:
|
||||
group_report = import_session.import_groups()
|
||||
|
||||
user_report = []
|
||||
if import_users:
|
||||
user_report = import_session.import_users()
|
||||
|
||||
import_session.clean_up()
|
||||
|
||||
return {
|
||||
"recipeImports": recipe_report,
|
||||
"settingsImports": settings_report,
|
||||
"themeImports": theme_report,
|
||||
"pageImports": page_report,
|
||||
"groupImports": group_report,
|
||||
"userImports": user_report,
|
||||
}
|
||||
|
||||
@@ -2,23 +2,23 @@ import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from app_config import IMG_DIR
|
||||
from utils.logger import logger
|
||||
from fastapi.logger import logger
|
||||
from mealie.core.config import app_dirs
|
||||
|
||||
|
||||
def read_image(recipe_slug: str) -> Path:
|
||||
if IMG_DIR.joinpath(recipe_slug).is_file():
|
||||
return IMG_DIR.joinpath(recipe_slug)
|
||||
else:
|
||||
recipe_slug = recipe_slug.split(".")[0]
|
||||
for file in IMG_DIR.glob(f"{recipe_slug}*"):
|
||||
return file
|
||||
if app_dirs.IMG_DIR.joinpath(recipe_slug).is_file():
|
||||
return app_dirs.IMG_DIR.joinpath(recipe_slug)
|
||||
|
||||
recipe_slug = recipe_slug.split(".")[0]
|
||||
for file in app_dirs.IMG_DIR.glob(f"{recipe_slug}*"):
|
||||
return file
|
||||
|
||||
|
||||
def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path.name:
|
||||
delete_image(recipe_slug)
|
||||
|
||||
image_path = Path(IMG_DIR.joinpath(f"{recipe_slug}.{extension}"))
|
||||
image_path = Path(app_dirs.IMG_DIR.joinpath(f"{recipe_slug}.{extension}"))
|
||||
with open(image_path, "ab") as f:
|
||||
f.write(file_data)
|
||||
|
||||
@@ -27,7 +27,7 @@ def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path.name
|
||||
|
||||
def delete_image(recipe_slug: str) -> str:
|
||||
recipe_slug = recipe_slug.split(".")[0]
|
||||
for file in IMG_DIR.glob(f"{recipe_slug}*"):
|
||||
for file in app_dirs.IMG_DIR.glob(f"{recipe_slug}*"):
|
||||
return file.unlink()
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ def scrape_image(image_url: str, slug: str) -> Path:
|
||||
image_url = image_url.get("url")
|
||||
|
||||
filename = slug + "." + image_url.split(".")[-1]
|
||||
filename = IMG_DIR.joinpath(filename)
|
||||
filename = app_dirs.IMG_DIR.joinpath(filename)
|
||||
|
||||
try:
|
||||
r = requests.get(image_url, stream=True)
|
||||
|
||||
@@ -1,105 +1,73 @@
|
||||
from datetime import date, timedelta
|
||||
from typing import List, Optional
|
||||
from typing import Union
|
||||
|
||||
from db.database import db
|
||||
from pydantic import BaseModel
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session
|
||||
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
|
||||
|
||||
from services.recipe_services import Recipe
|
||||
|
||||
def process_meals(session: Session, meal_plan_base: MealPlanIn) -> MealPlanProcessed:
|
||||
meals = []
|
||||
for x, meal in enumerate(meal_plan_base.meals):
|
||||
meal: MealIn
|
||||
try:
|
||||
recipe: Recipe = db.recipes.get(session, meal.slug)
|
||||
|
||||
meal_data = MealOut(
|
||||
slug=recipe.slug,
|
||||
name=recipe.name,
|
||||
date=meal_plan_base.startDate + timedelta(days=x),
|
||||
image=recipe.image,
|
||||
description=recipe.description,
|
||||
)
|
||||
|
||||
except:
|
||||
|
||||
meal_data = MealOut(
|
||||
date=meal_plan_base.startDate + timedelta(days=x),
|
||||
)
|
||||
|
||||
meals.append(meal_data)
|
||||
|
||||
return MealPlanProcessed(
|
||||
group=meal_plan_base.group,
|
||||
meals=meals,
|
||||
startDate=meal_plan_base.startDate,
|
||||
endDate=meal_plan_base.endDate,
|
||||
)
|
||||
|
||||
|
||||
class Meal(BaseModel):
|
||||
slug: Optional[str]
|
||||
name: Optional[str]
|
||||
date: date
|
||||
dateText: str
|
||||
image: Optional[str]
|
||||
description: Optional[str]
|
||||
def get_todays_meal(session: Session, group: Union[int, GroupInDB]) -> Recipe:
|
||||
"""Returns the given mealplan for today based off the group. If the group
|
||||
Type is of type int, then a query will be made to the database to get the
|
||||
grop object."
|
||||
|
||||
Args:
|
||||
session (Session): SqlAlchemy Session
|
||||
group (Union[int, GroupInDB]): Either the id of the group or the GroupInDB Object
|
||||
|
||||
class MealData(BaseModel):
|
||||
name: Optional[str]
|
||||
slug: str
|
||||
dateText: str
|
||||
Returns:
|
||||
Recipe: Pydantic Recipe Object
|
||||
"""
|
||||
session = session if session else create_session()
|
||||
|
||||
if isinstance(group, int):
|
||||
group: GroupInDB = db.groups.get(session, group)
|
||||
|
||||
class MealPlan(BaseModel):
|
||||
uid: Optional[str]
|
||||
startDate: date
|
||||
endDate: date
|
||||
meals: List[Meal]
|
||||
today_slug = None
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"startDate": date.today(),
|
||||
"endDate": date.today(),
|
||||
"meals": [
|
||||
{"slug": "Packed Mac and Cheese", "date": date.today()},
|
||||
{"slug": "Eggs and Toast", "date": date.today()},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
def process_meals(self, session: Session):
|
||||
meals = []
|
||||
for x, meal in enumerate(self.meals):
|
||||
|
||||
try:
|
||||
recipe = Recipe.get_by_slug(session, meal.slug)
|
||||
|
||||
meal_data = {
|
||||
"slug": recipe.slug,
|
||||
"name": recipe.name,
|
||||
"date": self.startDate + timedelta(days=x),
|
||||
"dateText": meal.dateText,
|
||||
"image": recipe.image,
|
||||
"description": recipe.description,
|
||||
}
|
||||
except:
|
||||
meal_data = {
|
||||
"date": self.startDate + timedelta(days=x),
|
||||
"dateText": meal.dateText,
|
||||
}
|
||||
|
||||
meals.append(Meal(**meal_data))
|
||||
|
||||
self.meals = meals
|
||||
|
||||
def save_to_db(self, session: Session):
|
||||
db.meals.create(session, self.dict())
|
||||
|
||||
@staticmethod
|
||||
def get_all(session: Session) -> List:
|
||||
|
||||
all_meals = [
|
||||
MealPlan(**x) for x in db.meals.get_all(session, order_by="startDate")
|
||||
]
|
||||
|
||||
return all_meals
|
||||
|
||||
def update(self, session, uid):
|
||||
db.meals.update(session, uid, self.dict())
|
||||
|
||||
@staticmethod
|
||||
def delete(session, uid):
|
||||
db.meals.delete(session, uid)
|
||||
|
||||
@staticmethod
|
||||
def today(session: Session) -> str:
|
||||
""" Returns the meal slug for Today """
|
||||
meal_plan = db.meals.get_all(session, limit=1, order_by="startDate")
|
||||
|
||||
meal_docs = [Meal(**meal) for meal in meal_plan["meals"]]
|
||||
|
||||
for meal in meal_docs:
|
||||
for mealplan in group.mealplans:
|
||||
mealplan: MealPlanInDB
|
||||
for meal in mealplan.meals:
|
||||
meal: MealOut
|
||||
if meal.date == date.today():
|
||||
return meal.slug
|
||||
today_slug = meal.slug
|
||||
break
|
||||
|
||||
return "No Meal Today"
|
||||
|
||||
@staticmethod
|
||||
def this_week(session: Session):
|
||||
meal_plan = db.meals.get_all(session, limit=1, order_by="startDate")
|
||||
|
||||
return meal_plan
|
||||
if today_slug:
|
||||
return db.recipes.get(session, today_slug)
|
||||
else:
|
||||
return None
|
||||
|
||||
0
mealie/services/migrations/__init__.py
Normal file
0
mealie/services/migrations/__init__.py
Normal file
@@ -2,10 +2,12 @@ import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from app_config import IMG_DIR, TEMP_DIR
|
||||
from services.recipe_services import Recipe
|
||||
from fastapi.logger import logger
|
||||
from mealie.core.config import app_dirs
|
||||
from mealie.db.database import db
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.utils.unzip import unpack_zip
|
||||
from sqlalchemy.orm.session import Session
|
||||
from utils.unzip import unpack_zip
|
||||
|
||||
try:
|
||||
from yaml import CLoader as Loader
|
||||
@@ -49,42 +51,42 @@ def read_chowdown_file(recipe_file: Path) -> Recipe:
|
||||
"tags": recipe_data.get("tags").split(","),
|
||||
}
|
||||
|
||||
new_recipe = Recipe(**reformat_data)
|
||||
reformated_list = [{"text": instruction} for instruction in reformat_data["recipeInstructions"]]
|
||||
|
||||
reformated_list = []
|
||||
for instruction in new_recipe.recipeInstructions:
|
||||
reformated_list.append({"text": instruction})
|
||||
reformat_data["recipeInstructions"] = reformated_list
|
||||
|
||||
new_recipe.recipeInstructions = reformated_list
|
||||
|
||||
return new_recipe
|
||||
return Recipe(**reformat_data)
|
||||
|
||||
|
||||
def chowdown_migrate(session: Session, zip_file: Path):
|
||||
|
||||
temp_dir = unpack_zip(zip_file)
|
||||
|
||||
with temp_dir as dir:
|
||||
image_dir = TEMP_DIR.joinpath(dir, zip_file.stem, "images")
|
||||
recipe_dir = TEMP_DIR.joinpath(dir, zip_file.stem, "_recipes")
|
||||
chow_dir = next(Path(dir).iterdir())
|
||||
image_dir = app_dirs.TEMP_DIR.joinpath(chow_dir, "images")
|
||||
recipe_dir = app_dirs.TEMP_DIR.joinpath(chow_dir, "_recipes")
|
||||
|
||||
failed_recipes = []
|
||||
successful_recipes = []
|
||||
for recipe in recipe_dir.glob("*.md"):
|
||||
try:
|
||||
new_recipe = read_chowdown_file(recipe)
|
||||
new_recipe.save_to_db(session)
|
||||
successful_recipes.append(recipe.stem)
|
||||
except:
|
||||
db.recipes.create(session, new_recipe.dict())
|
||||
successful_recipes.append(new_recipe.name)
|
||||
except Exception as inst:
|
||||
session.rollback()
|
||||
logger.error(inst)
|
||||
failed_recipes.append(recipe.stem)
|
||||
|
||||
failed_images = []
|
||||
for image in image_dir.iterdir():
|
||||
try:
|
||||
if not image.stem in failed_recipes:
|
||||
shutil.copy(image, IMG_DIR.joinpath(image.name))
|
||||
except:
|
||||
if image.stem not in failed_recipes:
|
||||
shutil.copy(image, app_dirs.IMG_DIR.joinpath(image.name))
|
||||
except Exception as inst:
|
||||
logger.error(inst)
|
||||
failed_images.append(image.name)
|
||||
|
||||
report = {"successful": successful_recipes, "failed": failed_recipes}
|
||||
|
||||
return report
|
||||
|
||||
@@ -4,10 +4,10 @@ import shutil
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from app_config import IMG_DIR, MIGRATION_DIR, TEMP_DIR
|
||||
from services.recipe_services import Recipe
|
||||
from services.scraper.cleaner import Cleaner
|
||||
from app_config import IMG_DIR, TEMP_DIR
|
||||
from mealie.core.config import app_dirs
|
||||
from mealie.db.database import db
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.services.scraper.cleaner import Cleaner
|
||||
|
||||
|
||||
def process_selection(selection: Path) -> Path:
|
||||
@@ -15,7 +15,7 @@ def process_selection(selection: Path) -> Path:
|
||||
return selection
|
||||
elif selection.suffix == ".zip":
|
||||
with zipfile.ZipFile(selection, "r") as zip_ref:
|
||||
nextcloud_dir = TEMP_DIR.joinpath("nextcloud")
|
||||
nextcloud_dir = app_dirs.TEMP_DIR.joinpath("nextcloud")
|
||||
nextcloud_dir.mkdir(exist_ok=False, parents=True)
|
||||
zip_ref.extractall(nextcloud_dir)
|
||||
return nextcloud_dir
|
||||
@@ -46,27 +46,27 @@ def import_recipes(recipe_dir: Path) -> Recipe:
|
||||
recipe = Recipe(**recipe_data)
|
||||
|
||||
if image:
|
||||
shutil.copy(image, IMG_DIR.joinpath(image_name))
|
||||
shutil.copy(image, app_dirs.IMG_DIR.joinpath(image_name))
|
||||
|
||||
return recipe
|
||||
|
||||
|
||||
def prep():
|
||||
try:
|
||||
shutil.rmtree(TEMP_DIR)
|
||||
shutil.rmtree(app_dirs.TEMP_DIR)
|
||||
except:
|
||||
pass
|
||||
TEMP_DIR.mkdir(exist_ok=True, parents=True)
|
||||
app_dirs.TEMP_DIR.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
|
||||
def cleanup():
|
||||
shutil.rmtree(TEMP_DIR)
|
||||
shutil.rmtree(app_dirs.TEMP_DIR)
|
||||
|
||||
|
||||
def migrate(session, selection: str):
|
||||
prep()
|
||||
MIGRATION_DIR.mkdir(exist_ok=True)
|
||||
selection = MIGRATION_DIR.joinpath(selection)
|
||||
app_dirs.MIGRATION_DIR.mkdir(exist_ok=True)
|
||||
selection = app_dirs.MIGRATION_DIR.joinpath(selection)
|
||||
|
||||
nextcloud_dir = process_selection(selection)
|
||||
|
||||
@@ -77,11 +77,12 @@ def migrate(session, selection: str):
|
||||
|
||||
try:
|
||||
recipe = import_recipes(dir)
|
||||
recipe.save_to_db(session)
|
||||
db.recipes.create(session, recipe.dict())
|
||||
|
||||
successful_imports.append(recipe.name)
|
||||
except:
|
||||
logging.error(f"Failed Nextcloud Import: {dir.name}")
|
||||
logging.exception('')
|
||||
logging.exception("")
|
||||
failed_imports.append(dir.name)
|
||||
|
||||
cleanup()
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from db.database import db
|
||||
from pydantic import BaseModel, validator
|
||||
from slugify import slugify
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from services.image_services import delete_image
|
||||
|
||||
|
||||
class RecipeNote(BaseModel):
|
||||
title: str
|
||||
text: str
|
||||
|
||||
|
||||
class RecipeStep(BaseModel):
|
||||
text: str
|
||||
|
||||
|
||||
class Recipe(BaseModel):
|
||||
# Standard Schema
|
||||
name: str
|
||||
description: Optional[str]
|
||||
image: Optional[Any]
|
||||
recipeYield: Optional[str]
|
||||
recipeIngredient: Optional[list]
|
||||
recipeInstructions: Optional[list]
|
||||
|
||||
totalTime: Optional[str] = None
|
||||
prepTime: Optional[str] = None
|
||||
performTime: Optional[str] = None
|
||||
|
||||
# Mealie Specific
|
||||
slug: Optional[str] = ""
|
||||
categories: Optional[List[str]] = []
|
||||
tags: Optional[List[str]] = []
|
||||
dateAdded: Optional[datetime.date]
|
||||
notes: Optional[List[RecipeNote]] = []
|
||||
rating: Optional[int] = 0
|
||||
orgURL: Optional[str] = ""
|
||||
extras: Optional[dict] = {}
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"name": "Chicken and Rice With Leeks and Salsa Verde",
|
||||
"description": "This one-skillet dinner gets deep oniony flavor from lots of leeks cooked down to jammy tenderness.",
|
||||
"image": "chicken-and-rice-with-leeks-and-salsa-verde.jpg",
|
||||
"recipeYield": "4 Servings",
|
||||
"recipeIngredient": [
|
||||
"1 1/2 lb. skinless, boneless chicken thighs (4-8 depending on size)",
|
||||
"Kosher salt, freshly ground pepper",
|
||||
"3 Tbsp. unsalted butter, divided",
|
||||
],
|
||||
"recipeInstructions": [
|
||||
{
|
||||
"text": "Season chicken with salt and pepper.",
|
||||
},
|
||||
],
|
||||
"slug": "chicken-and-rice-with-leeks-and-salsa-verde",
|
||||
"tags": ["favorite", "yummy!"],
|
||||
"categories": ["Dinner", "Pasta"],
|
||||
"notes": [{"title": "Watch Out!", "text": "Prep the day before!"}],
|
||||
"orgURL": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde",
|
||||
"rating": 3,
|
||||
"extras": {"message": "Don't forget to defrost the chicken!"},
|
||||
}
|
||||
}
|
||||
|
||||
@validator("slug", always=True, pre=True)
|
||||
def validate_slug(slug: str, values):
|
||||
name: str = values["name"]
|
||||
calc_slug: str = slugify(name)
|
||||
|
||||
if slug == calc_slug:
|
||||
return slug
|
||||
else:
|
||||
slug = calc_slug
|
||||
return slug
|
||||
|
||||
@classmethod
|
||||
def get_by_slug(cls, session, slug: str):
|
||||
""" Returns a Recipe Object by Slug """
|
||||
|
||||
document = db.recipes.get(session, slug, "slug")
|
||||
|
||||
return cls(**document)
|
||||
|
||||
def save_to_db(self, session) -> str:
|
||||
recipe_dict = self.dict()
|
||||
|
||||
try:
|
||||
extension = Path(recipe_dict["image"]).suffix
|
||||
recipe_dict["image"] = recipe_dict.get("slug") + extension
|
||||
except:
|
||||
recipe_dict["image"] = "no image"
|
||||
|
||||
recipe_doc = db.recipes.create(session, recipe_dict)
|
||||
recipe = Recipe(**recipe_doc)
|
||||
|
||||
return recipe.slug
|
||||
|
||||
@staticmethod
|
||||
def delete(session: Session, recipe_slug: str) -> str:
|
||||
""" Removes the recipe from the database by slug """
|
||||
delete_image(recipe_slug)
|
||||
db.recipes.delete(session, recipe_slug)
|
||||
return "Document Deleted"
|
||||
|
||||
def update(self, session: Session, recipe_slug: str):
|
||||
""" Updates the recipe from the database by slug"""
|
||||
updated_slug = db.recipes.update(session, recipe_slug, self.dict())
|
||||
return updated_slug.get("slug")
|
||||
|
||||
@staticmethod
|
||||
def update_image(session: Session, slug: str, extension: str = None) -> str:
|
||||
"""A helper function to pass the new image name and extension
|
||||
into the database.
|
||||
|
||||
Args:
|
||||
slug (str): The current recipe slug
|
||||
extension (str): the file extension of the new image
|
||||
"""
|
||||
return db.recipes.update_image(session, slug, extension)
|
||||
|
||||
@staticmethod
|
||||
def get_all(session: Session):
|
||||
return db.recipes.get_all(session)
|
||||
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,32 +1,35 @@
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from db.db_setup import create_session
|
||||
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.logger import logger
|
||||
from models.settings_models import SiteSettings
|
||||
from db.database import db
|
||||
from utils.post_webhooks import post_webhooks
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session
|
||||
from fastapi.logger import logger
|
||||
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
|
||||
|
||||
|
||||
@scheduler.scheduled_job(trigger="interval", minutes=15)
|
||||
# TODO Fix Scheduler
|
||||
@scheduler.scheduled_job(trigger="interval", minutes=30)
|
||||
def update_webhook_schedule():
|
||||
"""
|
||||
A scheduled background job that runs every 15 minutes to
|
||||
A scheduled background job that runs every 30 minutes to
|
||||
poll the database for changes and reschedule the webhook time
|
||||
"""
|
||||
session = create_session()
|
||||
settings = db.settings.get(session, "main")
|
||||
settings = SiteSettings(**settings)
|
||||
time = cron_parser(settings.webhooks.webhookTime)
|
||||
job = JOB_STORE.get("webhooks")
|
||||
all_groups: list[GroupInDB] = db.groups.get_all(session)
|
||||
|
||||
scheduler.reschedule_job(
|
||||
job.scheduled_task.id,
|
||||
trigger="cron",
|
||||
hour=time.hours,
|
||||
minute=time.minutes,
|
||||
)
|
||||
for group in all_groups:
|
||||
|
||||
time = cron_parser(group.webhook_time)
|
||||
job = JOB_STORE.get(group.name)
|
||||
|
||||
scheduler.reschedule_job(
|
||||
job.scheduled_task.id,
|
||||
trigger="cron",
|
||||
hour=time.hours,
|
||||
minute=time.minutes,
|
||||
)
|
||||
|
||||
session.close()
|
||||
logger.info(scheduler.print_jobs())
|
||||
@@ -34,7 +37,12 @@ def update_webhook_schedule():
|
||||
|
||||
class ScheduledFunction:
|
||||
def __init__(
|
||||
self, scheduler: BackgroundScheduler, function, cron: Cron, name: str
|
||||
self,
|
||||
scheduler: BackgroundScheduler,
|
||||
function,
|
||||
cron: Cron,
|
||||
name: str,
|
||||
args: list = None,
|
||||
) -> None:
|
||||
self.scheduled_task = scheduler.add_job(
|
||||
function,
|
||||
@@ -44,21 +52,41 @@ class ScheduledFunction:
|
||||
minute=cron.minutes,
|
||||
max_instances=1,
|
||||
replace_existing=True,
|
||||
args=args,
|
||||
)
|
||||
|
||||
logger.info("New Function Scheduled")
|
||||
logger.info(scheduler.print_jobs())
|
||||
|
||||
def init_webhook_schedule(scheduler, job_store: dict):
|
||||
session = create_session()
|
||||
all_groups: list[GroupInDB] = db.groups.get_all(session)
|
||||
|
||||
for group in all_groups:
|
||||
cron = cron_parser(group.webhook_time)
|
||||
|
||||
job_store.update(
|
||||
{
|
||||
group.name: ScheduledFunction(
|
||||
scheduler,
|
||||
post_webhooks,
|
||||
cron=cron,
|
||||
name=group.name,
|
||||
args=[group.id],
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
session.close()
|
||||
|
||||
return job_store
|
||||
|
||||
|
||||
logger.info("----INIT SCHEDULE OBJECT-----")
|
||||
|
||||
JOB_STORE = {
|
||||
"backup_job": ScheduledFunction(
|
||||
scheduler, auto_backup_job, Cron(hours=00, minutes=00), "backups"
|
||||
),
|
||||
"webhooks": ScheduledFunction(
|
||||
scheduler, post_webhooks, Cron(hours=00, minutes=00), "webhooks"
|
||||
),
|
||||
"backup_job": ScheduledFunction(scheduler, auto_backup_job, Cron(hours=00, minutes=00), "backups"),
|
||||
}
|
||||
|
||||
JOB_STORE = init_webhook_schedule(scheduler=scheduler, job_store=JOB_STORE)
|
||||
|
||||
logger.info(scheduler.print_jobs())
|
||||
scheduler.start()
|
||||
|
||||
0
mealie/services/scraper/__init__.py
Normal file
0
mealie/services/scraper/__init__.py
Normal file
@@ -1,5 +1,6 @@
|
||||
import html
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from slugify import slugify
|
||||
@@ -24,38 +25,44 @@ class Cleaner:
|
||||
Returns:
|
||||
dict: cleaned recipe dictionary
|
||||
"""
|
||||
recipe_data["totalTime"] = Cleaner.time(recipe_data.get("totalTime"))
|
||||
recipe_data["description"] = Cleaner.html(recipe_data.get("description", ""))
|
||||
recipe_data["prepTime"] = Cleaner.time(recipe_data.get("prepTime"))
|
||||
recipe_data["performTime"] = Cleaner.time(recipe_data.get("performTime"))
|
||||
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["image"])
|
||||
recipe_data["slug"] = slugify(recipe_data["name"])
|
||||
|
||||
# Times
|
||||
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["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
|
||||
def html(raw_html):
|
||||
cleanr = re.compile("<.*?>")
|
||||
cleantext = re.sub(cleanr, "", raw_html)
|
||||
return cleantext
|
||||
def category(category: str):
|
||||
if isinstance(category, str):
|
||||
return [category]
|
||||
else:
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def image(image) -> str:
|
||||
if type(image) == list:
|
||||
def html(raw_html):
|
||||
cleanr = re.compile("<.*?>")
|
||||
return re.sub(cleanr, "", raw_html)
|
||||
|
||||
@staticmethod
|
||||
def image(image=None) -> str:
|
||||
if not image:
|
||||
return "no image"
|
||||
if isinstance(image, list):
|
||||
return image[0]
|
||||
elif type(image) == dict:
|
||||
elif isinstance(image, dict):
|
||||
return image["url"]
|
||||
elif type(image) == str:
|
||||
elif isinstance(image, str):
|
||||
return image
|
||||
else:
|
||||
raise Exception(f"Unrecognised image URL format: {image}")
|
||||
@@ -66,34 +73,25 @@ class Cleaner:
|
||||
return []
|
||||
|
||||
# One long string split by (possibly multiple) new lines
|
||||
if type(instructions) == str:
|
||||
return [
|
||||
{"text": Cleaner._instruction(line)}
|
||||
for line in instructions.splitlines()
|
||||
if line
|
||||
]
|
||||
if isinstance(instructions, str):
|
||||
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:
|
||||
elif isinstance(instructions, list) and isinstance(instructions[0], str):
|
||||
return [{"text": Cleaner._instruction(step)} for step in instructions]
|
||||
|
||||
# Dictionaries (let's assume it's a HowToStep) in a list
|
||||
elif type(instructions) == list and type(instructions[0]) == dict:
|
||||
elif isinstance(instructions, list) and isinstance(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
|
||||
sectionSteps = []
|
||||
for step in instructions:
|
||||
if step["@type"] == "HowToSection":
|
||||
[sectionSteps.append(item) for item in step["itemListELement"]]
|
||||
[sectionSteps.append(item) for item in step["itemListElement"]]
|
||||
|
||||
if len(sectionSteps) > 0:
|
||||
return [
|
||||
@@ -108,6 +106,7 @@ class Cleaner:
|
||||
if step["@type"] == "HowToStep"
|
||||
]
|
||||
except Exception as e:
|
||||
print(e)
|
||||
# Not "@type", try "type"
|
||||
try:
|
||||
return [
|
||||
@@ -123,11 +122,11 @@ class Cleaner:
|
||||
|
||||
@staticmethod
|
||||
def _instruction(line) -> str:
|
||||
l = Cleaner.html(line.strip())
|
||||
clean_line = Cleaner.html(line.strip())
|
||||
# Some sites erroneously escape their strings on multiple levels
|
||||
while not l == (l := html.unescape(l)):
|
||||
while not clean_line == (clean_line := html.unescape(clean_line)):
|
||||
pass
|
||||
return l
|
||||
return clean_line
|
||||
|
||||
@staticmethod
|
||||
def ingredient(ingredients: list) -> str:
|
||||
@@ -136,14 +135,16 @@ class Cleaner:
|
||||
|
||||
@staticmethod
|
||||
def yield_amount(yld) -> str:
|
||||
if type(yld) == list:
|
||||
if isinstance(yld, list):
|
||||
return yld[-1]
|
||||
else:
|
||||
return yld
|
||||
|
||||
@staticmethod
|
||||
def time(time_entry) -> str:
|
||||
if type(time_entry) == type(None):
|
||||
def time(time_entry):
|
||||
if time_entry is None:
|
||||
return None
|
||||
elif type(time_entry) != str:
|
||||
elif isinstance(time_entry, datetime):
|
||||
print(time_entry)
|
||||
else:
|
||||
return str(time_entry)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from typing import Tuple
|
||||
|
||||
import extruct
|
||||
from app_config import DEBUG_DIR
|
||||
from mealie.core.config import app_dirs
|
||||
from slugify import slugify
|
||||
from w3lib.html import get_base_url
|
||||
|
||||
LAST_JSON = DEBUG_DIR.joinpath("last_recipe.json")
|
||||
LAST_JSON = app_dirs.DEBUG_DIR.joinpath("last_recipe.json")
|
||||
|
||||
|
||||
def og_field(properties: dict, field_name: str) -> str:
|
||||
|
||||
@@ -3,14 +3,14 @@ from typing import List
|
||||
|
||||
import requests
|
||||
import scrape_schema_recipe
|
||||
from app_config import DEBUG_DIR
|
||||
from services.image_services import scrape_image
|
||||
from services.recipe_services import Recipe
|
||||
from services.scraper import open_graph
|
||||
from services.scraper.cleaner import Cleaner
|
||||
from utils.logger import logger
|
||||
from mealie.core.config import app_dirs
|
||||
from fastapi.logger import logger
|
||||
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")
|
||||
LAST_JSON = app_dirs.DEBUG_DIR.joinpath("last_recipe.json")
|
||||
|
||||
|
||||
def create_from_url(url: str) -> Recipe:
|
||||
@@ -28,24 +28,18 @@ def create_from_url(url: str) -> Recipe:
|
||||
new_recipe = Cleaner.clean(new_recipe, url)
|
||||
new_recipe = download_image_for_recipe(new_recipe)
|
||||
|
||||
recipe = Recipe(**new_recipe)
|
||||
|
||||
return recipe
|
||||
return Recipe(**new_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
|
||||
print(e)
|
||||
scraped_recipes: List[dict] = scrape_schema_recipe.loads(html)
|
||||
dump_last_json(scraped_recipes)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user