From f5fa4040bf466ceeb51e2951894231cba4ac9c44 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Tue, 23 Feb 2021 15:15:55 -0900 Subject: [PATCH] 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 --- .gitignore | 1 + frontend/src/App.vue | 19 +- frontend/src/api.js | 23 -- frontend/src/api/api-utils.js | 5 + frontend/src/api/index.js | 24 ++ frontend/src/api/recipe.js | 2 +- frontend/src/api/themes.js | 1 - frontend/src/api/users.js | 49 ++++ .../src/components/Admin/AdminSidebar.vue | 139 ++++++++++ .../Backup/AvailableBackupCard.vue | 0 .../{Settings => Admin}/Backup/BackupCard.vue | 0 .../Backup/ImportDialog.vue | 0 .../Backup/ImportSummaryDialog/DataTable.vue | 0 .../Backup/ImportSummaryDialog/index.vue | 26 +- .../Backup/NewBackupCard.vue | 0 .../General/HomePageSettings.vue | 55 ++-- .../MealPlanner/TimePickerDialog.vue | 0 .../Migration/MigrationCard.vue | 2 +- .../Theme/ColorPickerDialog.vue | 0 .../Theme/NewThemeDialog.vue | 0 frontend/src/components/Login/LoginDialog.vue | 29 +++ frontend/src/components/Login/LoginForm.vue | 145 +++++++++++ .../src/components/MealPlan/MealPlanCard.vue | 2 +- .../EditorButtonRow.vue} | 2 +- .../components/{UI => Recipe}/RecipeCard.vue | 0 frontend/src/components/UI/AddRecipeFab.vue | 32 ++- frontend/src/components/UI/CardSection.vue | 2 +- frontend/src/components/UI/Confirmation.vue | 33 ++- frontend/src/components/UI/LanguageMenu.vue | 80 ++++++ frontend/src/components/UI/Login.vue | 99 ------- .../components/UI/{ => Search}/SearchBar.vue | 0 .../UI/{ => Search}/SearchDialog.vue | 4 +- frontend/src/components/UI/SearchHeader.vue | 27 -- .../components/UI/{Menu.vue => SiteMenu.vue} | 49 +++- frontend/src/components/UI/SnackBar.vue | 41 --- frontend/src/locales/en.json | 5 +- .../Settings => pages/Admin}/Backup/index.vue | 10 +- .../src/pages/Admin/ManageUsers/index.vue | 243 ++++++++++++++++++ .../Admin}/MealPlanner/index.vue | 2 +- .../Admin}/Migration/index.vue | 48 ++-- frontend/src/pages/Admin/Profile/index.vue | 92 +++++++ .../Admin/Settings}/index.vue | 24 +- .../Settings => pages/Admin}/Theme/index.vue | 6 +- frontend/src/pages/Admin/index.vue | 20 ++ frontend/src/pages/LoginPage.vue | 19 ++ frontend/src/pages/RecipeNewPage.vue | 6 +- frontend/src/pages/RecipePage.vue | 6 +- frontend/src/pages/SearchPage.vue | 4 +- frontend/src/pages/SettingsPage.vue | 90 ------- frontend/src/routes.js | 40 --- frontend/src/routes/admin.js | 48 ++++ frontend/src/routes/index.js | 51 ++++ frontend/src/store/modules/userSettings.js | 14 + frontend/src/store/store.js | 19 -- frontend/vue.config.js | 8 + mealie/app.py | 22 +- mealie/{app_config.py => core/config.py} | 9 +- mealie/core/security.py | 29 +++ mealie/db/database.py | 16 +- mealie/db/db_base.py | 10 +- mealie/db/db_setup.py | 4 +- mealie/db/init_db.py | 63 +++++ mealie/db/models/_all_models.py | 5 + mealie/db/{sql => models}/db_session.py | 4 +- .../meal_models.py => models/mealplan.py} | 2 +- mealie/db/{sql => models}/model_base.py | 0 .../recipe_models.py => models/recipe.py} | 4 +- .../settings_models.py => models/settings.py} | 3 +- .../{sql/theme_models.py => models/theme.py} | 2 +- mealie/db/models/users.py | 44 ++++ mealie/db/sql/_all_models.py | 4 - mealie/routes/__init__.py | 0 mealie/routes/backup_routes.py | 6 +- mealie/routes/debug_routes.py | 4 +- mealie/routes/deps.py | 23 ++ mealie/routes/meal_routes.py | 2 +- mealie/routes/migration_routes.py | 6 +- mealie/routes/recipe/all_recipe_routes.py | 2 +- mealie/routes/recipe/category_routes.py | 7 +- mealie/routes/recipe/recipe_crud_routes.py | 4 +- mealie/routes/recipe/tag_routes.py | 4 +- mealie/routes/setting_routes.py | 8 +- mealie/routes/theme_routes.py | 4 +- mealie/routes/users/__init__.py | 0 mealie/routes/users/auth.py | 32 +++ mealie/routes/users/crud.py | 85 ++++++ mealie/routes/users/users.py | 7 + .../backup_models.py => schema/backup.py} | 0 .../category_models.py => schema/category.py} | 0 .../{models/meal_models.py => schema/meal.py} | 0 .../migration.py} | 0 .../recipe_models.py => schema/recipe.py} | 0 .../import_models.py => schema/restore.py} | 0 .../settings_models.py => schema/settings.py} | 0 mealie/{utils => schema}/snackbar.py | 0 .../theme_models.py => schema/theme.py} | 0 mealie/schema/user.py | 32 +++ mealie/services/backups/exports.py | 4 +- mealie/services/backups/imports.py | 8 +- mealie/services/image_services.py | 4 +- mealie/services/migrations/chowdown.py | 2 +- mealie/services/migrations/nextcloud.py | 6 +- mealie/services/scheduler/scheduled_jobs.py | 4 +- mealie/services/scraper/open_graph.py | 2 +- mealie/services/scraper/scraper.py | 8 +- mealie/services/settings_services.py | 17 -- mealie/services/theme_services.py | 28 -- mealie/services/users/__init__.py | 0 mealie/tests/conftest.py | 25 +- .../tests/test_migrations/test_nextcloud.py | 12 +- .../test_routes/test_migration_routes.py | 2 +- .../tests/test_routes/test_settings_routes.py | 2 - mealie/tests/test_routes/test_user_routes.py | 93 +++++++ .../test_migrations/test_nextcloud.py | 12 +- mealie/utils/api_docs.py | 5 +- mealie/utils/global_scheduler.py | 11 - mealie/utils/logger.py | 25 -- mealie/utils/post_webhooks.py | 2 +- mealie/utils/startup.py | 13 - mealie/utils/unzip.py | 2 +- poetry.lock | 219 ++++++++++++++-- pyproject.toml | 5 +- 122 files changed, 1909 insertions(+), 695 deletions(-) delete mode 100644 frontend/src/api.js create mode 100644 frontend/src/api/index.js create mode 100644 frontend/src/api/users.js create mode 100644 frontend/src/components/Admin/AdminSidebar.vue rename frontend/src/components/{Settings => Admin}/Backup/AvailableBackupCard.vue (100%) rename frontend/src/components/{Settings => Admin}/Backup/BackupCard.vue (100%) rename frontend/src/components/{Settings => Admin}/Backup/ImportDialog.vue (100%) rename frontend/src/components/{Settings => Admin}/Backup/ImportSummaryDialog/DataTable.vue (100%) rename frontend/src/components/{Settings => Admin}/Backup/ImportSummaryDialog/index.vue (89%) rename frontend/src/components/{Settings => Admin}/Backup/NewBackupCard.vue (100%) rename frontend/src/components/{Settings => Admin}/General/HomePageSettings.vue (81%) rename frontend/src/components/{Settings => Admin}/MealPlanner/TimePickerDialog.vue (100%) rename frontend/src/components/{Settings => Admin}/Migration/MigrationCard.vue (97%) rename frontend/src/components/{Settings => Admin}/Theme/ColorPickerDialog.vue (100%) rename frontend/src/components/{Settings => Admin}/Theme/NewThemeDialog.vue (100%) create mode 100644 frontend/src/components/Login/LoginDialog.vue create mode 100644 frontend/src/components/Login/LoginForm.vue rename frontend/src/components/{UI/ButtonRow.vue => Recipe/EditorButtonRow.vue} (96%) rename frontend/src/components/{UI => Recipe}/RecipeCard.vue (100%) create mode 100644 frontend/src/components/UI/LanguageMenu.vue delete mode 100644 frontend/src/components/UI/Login.vue rename frontend/src/components/UI/{ => Search}/SearchBar.vue (100%) rename frontend/src/components/UI/{ => Search}/SearchDialog.vue (95%) delete mode 100644 frontend/src/components/UI/SearchHeader.vue rename frontend/src/components/UI/{Menu.vue => SiteMenu.vue} (56%) delete mode 100644 frontend/src/components/UI/SnackBar.vue rename frontend/src/{components/Settings => pages/Admin}/Backup/index.vue (87%) create mode 100644 frontend/src/pages/Admin/ManageUsers/index.vue rename frontend/src/{components/Settings => pages/Admin}/MealPlanner/index.vue (98%) rename frontend/src/{components/Settings => pages/Admin}/Migration/index.vue (71%) create mode 100644 frontend/src/pages/Admin/Profile/index.vue rename frontend/src/{components/Settings/General => pages/Admin/Settings}/index.vue (62%) rename frontend/src/{components/Settings => pages/Admin}/Theme/index.vue (97%) create mode 100644 frontend/src/pages/Admin/index.vue create mode 100644 frontend/src/pages/LoginPage.vue delete mode 100644 frontend/src/pages/SettingsPage.vue delete mode 100644 frontend/src/routes.js create mode 100644 frontend/src/routes/admin.js create mode 100644 frontend/src/routes/index.js rename mealie/{app_config.py => core/config.py} (92%) create mode 100644 mealie/core/security.py create mode 100644 mealie/db/init_db.py create mode 100644 mealie/db/models/_all_models.py rename mealie/db/{sql => models}/db_session.py (88%) rename mealie/db/{sql/meal_models.py => models/mealplan.py} (97%) rename mealie/db/{sql => models}/model_base.py (100%) rename mealie/db/{sql/recipe_models.py => models/recipe.py} (99%) rename mealie/db/{sql/settings_models.py => models/settings.py} (96%) rename mealie/db/{sql/theme_models.py => models/theme.py} (96%) create mode 100644 mealie/db/models/users.py delete mode 100644 mealie/db/sql/_all_models.py create mode 100644 mealie/routes/__init__.py create mode 100644 mealie/routes/deps.py create mode 100644 mealie/routes/users/__init__.py create mode 100644 mealie/routes/users/auth.py create mode 100644 mealie/routes/users/crud.py create mode 100644 mealie/routes/users/users.py rename mealie/{models/backup_models.py => schema/backup.py} (100%) rename mealie/{models/category_models.py => schema/category.py} (100%) rename mealie/{models/meal_models.py => schema/meal.py} (100%) rename mealie/{models/migration_models.py => schema/migration.py} (100%) rename mealie/{models/recipe_models.py => schema/recipe.py} (100%) rename mealie/{models/import_models.py => schema/restore.py} (100%) rename mealie/{models/settings_models.py => schema/settings.py} (100%) rename mealie/{utils => schema}/snackbar.py (100%) rename mealie/{models/theme_models.py => schema/theme.py} (100%) create mode 100644 mealie/schema/user.py delete mode 100644 mealie/services/settings_services.py delete mode 100644 mealie/services/theme_services.py create mode 100644 mealie/services/users/__init__.py create mode 100644 mealie/tests/test_routes/test_user_routes.py delete mode 100644 mealie/utils/global_scheduler.py delete mode 100644 mealie/utils/logger.py delete mode 100644 mealie/utils/startup.py diff --git a/.gitignore b/.gitignore index 93d96aca7..88de70f24 100644 --- a/.gitignore +++ b/.gitignore @@ -153,3 +153,4 @@ node_modules/ mealie/data/debug/last_recipe.json *.sqlite app_data/db/test.db +scratch.py diff --git a/frontend/src/App.vue b/frontend/src/App.vue index bc7da4c75..78e21ae02 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -27,12 +27,12 @@ mdi-magnify - + + - @@ -41,19 +41,20 @@ + + \ No newline at end of file diff --git a/frontend/src/components/Settings/Backup/AvailableBackupCard.vue b/frontend/src/components/Admin/Backup/AvailableBackupCard.vue similarity index 100% rename from frontend/src/components/Settings/Backup/AvailableBackupCard.vue rename to frontend/src/components/Admin/Backup/AvailableBackupCard.vue diff --git a/frontend/src/components/Settings/Backup/BackupCard.vue b/frontend/src/components/Admin/Backup/BackupCard.vue similarity index 100% rename from frontend/src/components/Settings/Backup/BackupCard.vue rename to frontend/src/components/Admin/Backup/BackupCard.vue diff --git a/frontend/src/components/Settings/Backup/ImportDialog.vue b/frontend/src/components/Admin/Backup/ImportDialog.vue similarity index 100% rename from frontend/src/components/Settings/Backup/ImportDialog.vue rename to frontend/src/components/Admin/Backup/ImportDialog.vue diff --git a/frontend/src/components/Settings/Backup/ImportSummaryDialog/DataTable.vue b/frontend/src/components/Admin/Backup/ImportSummaryDialog/DataTable.vue similarity index 100% rename from frontend/src/components/Settings/Backup/ImportSummaryDialog/DataTable.vue rename to frontend/src/components/Admin/Backup/ImportSummaryDialog/DataTable.vue diff --git a/frontend/src/components/Settings/Backup/ImportSummaryDialog/index.vue b/frontend/src/components/Admin/Backup/ImportSummaryDialog/index.vue similarity index 89% rename from frontend/src/components/Settings/Backup/ImportSummaryDialog/index.vue rename to frontend/src/components/Admin/Backup/ImportSummaryDialog/index.vue index 718a2dfd2..5ba5b0926 100644 --- a/frontend/src/components/Settings/Backup/ImportSummaryDialog/index.vue +++ b/frontend/src/components/Admin/Backup/ImportSummaryDialog/index.vue @@ -2,10 +2,18 @@
- Import Summary - - - + + + mdi-import + + + Import Summary + + + + + +

Recipes

@@ -17,8 +25,8 @@ Failed: {{ recipeNumbers.failure }}
- - +
+

Themes

@@ -30,8 +38,8 @@ Failed: {{ themeNumbers.failure }}
- - +
+

Settings

@@ -43,7 +51,7 @@ Failed: {{ settingsNumbers.failure }}
- +
diff --git a/frontend/src/components/Settings/Backup/NewBackupCard.vue b/frontend/src/components/Admin/Backup/NewBackupCard.vue similarity index 100% rename from frontend/src/components/Settings/Backup/NewBackupCard.vue rename to frontend/src/components/Admin/Backup/NewBackupCard.vue diff --git a/frontend/src/components/Settings/General/HomePageSettings.vue b/frontend/src/components/Admin/General/HomePageSettings.vue similarity index 81% rename from frontend/src/components/Settings/General/HomePageSettings.vue rename to frontend/src/components/Admin/General/HomePageSettings.vue index 8e703022e..edc6cf968 100644 --- a/frontend/src/components/Settings/General/HomePageSettings.vue +++ b/frontend/src/components/Admin/General/HomePageSettings.vue @@ -25,17 +25,19 @@ - - -

{{$t('settings.homepage.homepage-categories')}}

-
- - + + + + mdi-home + + + + Home Page Categories + + + + +
- - -

- {{$t('settings.homepage.all-categories')}} - - - mdi-plus - -

-
- - + + + + mdi-tag + + + + All Categories + + + + + - + {{ title }} diff --git a/frontend/src/components/Settings/Theme/ColorPickerDialog.vue b/frontend/src/components/Admin/Theme/ColorPickerDialog.vue similarity index 100% rename from frontend/src/components/Settings/Theme/ColorPickerDialog.vue rename to frontend/src/components/Admin/Theme/ColorPickerDialog.vue diff --git a/frontend/src/components/Settings/Theme/NewThemeDialog.vue b/frontend/src/components/Admin/Theme/NewThemeDialog.vue similarity index 100% rename from frontend/src/components/Settings/Theme/NewThemeDialog.vue rename to frontend/src/components/Admin/Theme/NewThemeDialog.vue diff --git a/frontend/src/components/Login/LoginDialog.vue b/frontend/src/components/Login/LoginDialog.vue new file mode 100644 index 000000000..5b7eb02be --- /dev/null +++ b/frontend/src/components/Login/LoginDialog.vue @@ -0,0 +1,29 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/Login/LoginForm.vue b/frontend/src/components/Login/LoginForm.vue new file mode 100644 index 000000000..bb837220b --- /dev/null +++ b/frontend/src/components/Login/LoginForm.vue @@ -0,0 +1,145 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/MealPlan/MealPlanCard.vue b/frontend/src/components/MealPlan/MealPlanCard.vue index b5c176638..100aceced 100644 --- a/frontend/src/components/MealPlan/MealPlanCard.vue +++ b/frontend/src/components/MealPlan/MealPlanCard.vue @@ -27,7 +27,7 @@ diff --git a/frontend/src/components/UI/LanguageMenu.vue b/frontend/src/components/UI/LanguageMenu.vue new file mode 100644 index 000000000..93cdeb0c5 --- /dev/null +++ b/frontend/src/components/UI/LanguageMenu.vue @@ -0,0 +1,80 @@ + + + + \ No newline at end of file diff --git a/frontend/src/components/UI/Login.vue b/frontend/src/components/UI/Login.vue deleted file mode 100644 index 91a33ff51..000000000 --- a/frontend/src/components/UI/Login.vue +++ /dev/null @@ -1,99 +0,0 @@ - - - - - \ No newline at end of file diff --git a/frontend/src/components/UI/SearchBar.vue b/frontend/src/components/UI/Search/SearchBar.vue similarity index 100% rename from frontend/src/components/UI/SearchBar.vue rename to frontend/src/components/UI/Search/SearchBar.vue diff --git a/frontend/src/components/UI/SearchDialog.vue b/frontend/src/components/UI/Search/SearchDialog.vue similarity index 95% rename from frontend/src/components/UI/SearchDialog.vue rename to frontend/src/components/UI/Search/SearchDialog.vue index cd245d69e..17b19c8a5 100644 --- a/frontend/src/components/UI/SearchDialog.vue +++ b/frontend/src/components/UI/Search/SearchDialog.vue @@ -43,8 +43,8 @@ - - \ No newline at end of file diff --git a/frontend/src/components/UI/Menu.vue b/frontend/src/components/UI/SiteMenu.vue similarity index 56% rename from frontend/src/components/UI/Menu.vue rename to frontend/src/components/UI/SiteMenu.vue index b8f39fc2e..b9123d8da 100644 --- a/frontend/src/components/UI/Menu.vue +++ b/frontend/src/components/UI/SiteMenu.vue @@ -1,5 +1,6 @@ - - \ No newline at end of file diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index ca47b8277..bdf4685ab 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -4,8 +4,9 @@ "take-me-home": "Take me Home" }, "new-recipe": { - "from-url": "From URL", + "from-url": "Import a Recipe", "recipe-url": "Recipe URL", + "url-form-hint": "Copy and paste a link from your favorite recipe website", "error-message": "Looks like there was an error parsing the URL. Check the log and debug/last_recipe.json to see what went wrong.", "bulk-add": "Bulk Add", "paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Paste in your recipe data. Each line will be treated as an item in a list" @@ -89,6 +90,8 @@ }, "settings": { "general-settings": "General Settings", + "change-password": "Change Password", + "admin-settings": "Admin Settings", "local-api": "Local API", "language": "Language", "add-a-new-theme": "Add a New Theme", diff --git a/frontend/src/components/Settings/Backup/index.vue b/frontend/src/pages/Admin/Backup/index.vue similarity index 87% rename from frontend/src/components/Settings/Backup/index.vue rename to frontend/src/pages/Admin/Backup/index.vue index 56d2998f6..1e6163183 100644 --- a/frontend/src/components/Settings/Backup/index.vue +++ b/frontend/src/pages/Admin/Backup/index.vue @@ -48,11 +48,11 @@ + + \ No newline at end of file diff --git a/frontend/src/components/Settings/MealPlanner/index.vue b/frontend/src/pages/Admin/MealPlanner/index.vue similarity index 98% rename from frontend/src/components/Settings/MealPlanner/index.vue rename to frontend/src/pages/Admin/MealPlanner/index.vue index 5d2a856db..feeb60841 100644 --- a/frontend/src/components/Settings/MealPlanner/index.vue +++ b/frontend/src/pages/Admin/MealPlanner/index.vue @@ -97,7 +97,7 @@ \ No newline at end of file diff --git a/frontend/src/components/Settings/General/index.vue b/frontend/src/pages/Admin/Settings/index.vue similarity index 62% rename from frontend/src/components/Settings/General/index.vue rename to frontend/src/pages/Admin/Settings/index.vue index 808c16694..13d2bc18f 100644 --- a/frontend/src/components/Settings/General/index.vue +++ b/frontend/src/pages/Admin/Settings/index.vue @@ -1,7 +1,7 @@ + + \ No newline at end of file diff --git a/frontend/src/pages/LoginPage.vue b/frontend/src/pages/LoginPage.vue new file mode 100644 index 000000000..3d8da7d63 --- /dev/null +++ b/frontend/src/pages/LoginPage.vue @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/pages/RecipeNewPage.vue b/frontend/src/pages/RecipeNewPage.vue index d841fe77b..a6d4b93eb 100644 --- a/frontend/src/pages/RecipeNewPage.vue +++ b/frontend/src/pages/RecipeNewPage.vue @@ -12,7 +12,7 @@
- - - - \ No newline at end of file diff --git a/frontend/src/routes.js b/frontend/src/routes.js deleted file mode 100644 index 5fbbde2a2..000000000 --- a/frontend/src/routes.js +++ /dev/null @@ -1,40 +0,0 @@ -import HomePage from "./pages/HomePage"; -import Page404 from "./pages/404Page"; -import SearchPage from "./pages/SearchPage"; -import RecipePage from "./pages/RecipePage"; -import RecipeNewPage from "./pages/RecipeNewPage"; -import SettingsPage from "./pages/SettingsPage"; -import AllRecipesPage from "./pages/AllRecipesPage"; -import CategoryPage from "./pages/CategoryPage"; -import MeaplPlanPage from "./pages/MealPlanPage"; -import Debug from "./pages/Debug"; -import MealPlanThisWeekPage from "./pages/MealPlanThisWeekPage"; -import api from "@/api"; - -export const routes = [ - { path: "/", component: HomePage }, - { path: "/mealie", component: HomePage }, - { path: "/debug", component: Debug }, - { path: "/search", component: SearchPage }, - { path: "/recipes/all", component: AllRecipesPage }, - { path: "/recipes/:category", component: CategoryPage }, - { path: "/recipe/:recipe", component: RecipePage }, - { path: "/new/", component: RecipeNewPage }, - { path: "/settings/site", component: SettingsPage }, - { path: "/meal-plan/planner", component: MeaplPlanPage }, - { path: "/meal-plan/this-week", component: MealPlanThisWeekPage }, - { - path: "/meal-plan/today", - beforeEnter: async (_to, _from, next) => { - await todaysMealRoute().then(redirect => { - next(redirect); - }); - }, - }, - { path: "*", component: Page404 }, -]; - -async function todaysMealRoute() { - const response = await api.mealPlans.today(); - return "/recipe/" + response.data; -} diff --git a/frontend/src/routes/admin.js b/frontend/src/routes/admin.js new file mode 100644 index 000000000..ee7f47e24 --- /dev/null +++ b/frontend/src/routes/admin.js @@ -0,0 +1,48 @@ +import Admin from "@/pages/Admin"; +import Backup from "@/pages/Admin/Backup"; +import Theme from "@/pages/Admin/Theme"; +import MealPlanner from "@/pages/Admin/MealPlanner"; +import Migration from "@/pages/Admin/Migration"; +import Profile from "@/pages/Admin/Profile"; +import ManageUsers from "@/pages/Admin/ManageUsers"; +import Settings from "@/pages/Admin/Settings"; + +export default { + path: "/admin", + component: Admin, + children: [ + { + path: "", + component: Profile, + }, + { + path: "profile", + component: Profile, + }, + + { + path: "backups", + component: Backup, + }, + { + path: "themes", + component: Theme, + }, + { + path: "meal-planner", + component: MealPlanner, + }, + { + path: "migrations", + component: Migration, + }, + { + path: "manage-users", + component: ManageUsers, + }, + { + path: "settings", + component: Settings, + }, + ], +}; diff --git a/frontend/src/routes/index.js b/frontend/src/routes/index.js new file mode 100644 index 000000000..60319b728 --- /dev/null +++ b/frontend/src/routes/index.js @@ -0,0 +1,51 @@ +import HomePage from "../pages/HomePage"; +import Page404 from "../pages/404Page"; +import SearchPage from "../pages/SearchPage"; +import RecipePage from "../pages/RecipePage"; +import RecipeNewPage from "../pages/RecipeNewPage"; +import AllRecipesPage from "../pages/AllRecipesPage"; +import CategoryPage from "../pages/CategoryPage"; +import MeaplPlanPage from "../pages/MealPlanPage"; +import Debug from "../pages/Debug"; +import LoginPage from "../pages/LoginPage"; +import MealPlanThisWeekPage from "../pages/MealPlanThisWeekPage"; +import api from "@/api"; +import Admin from "./admin"; +import { store } from "../store/store"; + +export const routes = [ + { path: "/", name: "home", component: HomePage }, + { + path: "/logout", + beforeEnter: (_to, _from, next) => { + store.commit("setToken", ""); + store.commit("setIsLoggedIn", false); + next("/"); + }, + }, + { path: "/mealie", component: HomePage }, + { path: "/login", component: LoginPage }, + { path: "/debug", component: Debug }, + { path: "/search", component: SearchPage }, + { path: "/recipes/all", component: AllRecipesPage }, + { path: "/recipes/:category", component: CategoryPage }, + { path: "/recipe/:recipe", component: RecipePage }, + { path: "/new/", component: RecipeNewPage }, + { path: "/meal-plan/planner", component: MeaplPlanPage }, + { path: "/meal-plan/this-week", component: MealPlanThisWeekPage }, + Admin, + { + path: "/meal-plan/today", + beforeEnter: async (_to, _from, next) => { + await todaysMealRoute().then(redirect => { + next(redirect); + }); + }, + }, + { path: "*", component: Page404 }, +]; + +async function todaysMealRoute() { + const response = await api.mealPlans.today(); + return "/recipe/" + response.data; +} diff --git a/frontend/src/store/modules/userSettings.js b/frontend/src/store/modules/userSettings.js index 2908837fc..39c8406de 100644 --- a/frontend/src/store/modules/userSettings.js +++ b/frontend/src/store/modules/userSettings.js @@ -1,5 +1,6 @@ import api from "@/api"; import Vuetify from "../../plugins/vuetify"; +import axios from "axios"; function inDarkMode(payload) { let isDark; @@ -18,6 +19,8 @@ const state = { activeTheme: {}, darkMode: "system", isDark: false, + isLoggedIn: false, + token: "", }; const mutations = { @@ -35,6 +38,14 @@ const mutations = { state.darkMode = payload; } }, + setIsLoggedIn(state, payload) { + state.isLoggedIn = payload; + }, + setToken(state, payload) { + state.isLoggedIn = true; + axios.defaults.headers.common["Authorization"] = `Bearer ${payload}`; + state.token = payload; + }, }; const actions = { @@ -47,6 +58,7 @@ const actions = { } }, + async initTheme({ dispatch, getters }) { //If theme is empty resetTheme if (Object.keys(getters.getActiveTheme).length === 0) { @@ -63,6 +75,8 @@ const getters = { getActiveTheme: state => state.activeTheme, getDarkMode: state => state.darkMode, getIsDark: state => state.isDark, + getIsLoggedIn: state => state.isLoggedIn, + getToken: state => state.token, }; export default { diff --git a/frontend/src/store/store.js b/frontend/src/store/store.js index a4af49226..19979f278 100644 --- a/frontend/src/store/store.js +++ b/frontend/src/store/store.js @@ -20,11 +20,6 @@ const store = new Vuex.Store({ homePage, }, state: { - // Home Page Settings - // Snackbar - snackActive: false, - snackText: "", - snackType: "warning", // All Recipe Data Store recentRecipes: [], @@ -33,15 +28,6 @@ const store = new Vuex.Store({ }, mutations: { - setSnackBar(state, payload) { - state.snackText = payload.text; - state.snackType = payload.type; - state.snackActive = true; - }, - setSnackActive(state, payload) { - state.snackActive = payload; - }, - setRecentRecipes(state, payload) { state.recentRecipes = payload; }, @@ -68,11 +54,6 @@ const store = new Vuex.Store({ }, getters: { - // - getSnackText: state => state.snackText, - getSnackActive: state => state.snackActive, - getSnackType: state => state.snackType, - getRecentRecipes: state => state.recentRecipes, getMealPlanCategories: state => state.mealPlanCategories, }, diff --git a/frontend/vue.config.js b/frontend/vue.config.js index d24793322..1507b9431 100644 --- a/frontend/vue.config.js +++ b/frontend/vue.config.js @@ -1,3 +1,4 @@ +const path = require("path"); module.exports = { transpileDependencies: ["vuetify"], publicPath: process.env.NODE_ENV === "production" ? "/" : "/", @@ -18,4 +19,11 @@ module.exports = { enableInSFC: true, }, }, + configureWebpack: { + resolve: { + alias: { + "@": path.resolve("src"), + }, + }, + }, }; diff --git a/mealie/app.py b/mealie/app.py index 1ac1fae83..250f0401a 100644 --- a/mealie/app.py +++ b/mealie/app.py @@ -2,7 +2,9 @@ import uvicorn from fastapi import FastAPI # import utils.startup as startup -from app_config import APP_VERSION, PORT, PRODUCTION, docs_url, redoc_url +from core.config import APP_VERSION, PORT, SECRET, docs_url, redoc_url +from db.db_setup import sql_exists +from db.init_db import init_db from routes import ( backup_routes, debug_routes, @@ -17,8 +19,8 @@ from routes.recipe import ( recipe_crud_routes, tag_routes, ) -from services.settings_services import default_settings_init -from utils.logger import logger +from routes.users import users +from fastapi.logger import logger app = FastAPI( title="Mealie", @@ -29,16 +31,17 @@ app = FastAPI( ) +def data_base_first_run(): + init_db() + + def start_scheduler(): import services.scheduler.scheduled_jobs -def init_settings(): - default_settings_init() - import services.theme_services - - def api_routers(): + # Authentication + app.include_router(users.router) # Recipes app.include_router(all_recipe_routes.router) app.include_router(category_routes.router) @@ -56,10 +59,11 @@ def api_routers(): app.include_router(debug_routes.router) +if not sql_exists: + data_base_first_run() api_routers() start_scheduler() -init_settings() if __name__ == "__main__": logger.info("-----SYSTEM STARTUP-----") diff --git a/mealie/app_config.py b/mealie/core/config.py similarity index 92% rename from mealie/app_config.py rename to mealie/core/config.py index 257421a20..d322ca232 100644 --- a/mealie/app_config.py +++ b/mealie/core/config.py @@ -15,9 +15,12 @@ def ensure_dirs(): ENV = CWD.joinpath(".env") dotenv.load_dotenv(ENV) + +SECRET = "super-secret-key" + # General APP_VERSION = "v0.3.0" -DB_VERSION = "v0.2.1" +DB_VERSION = "v0.3.0" PRODUCTION = os.environ.get("ENV") PORT = int(os.getenv("mealie_port", 9000)) API = os.getenv("api_docs", True) @@ -30,7 +33,7 @@ else: redoc_url = None # Helpful Globals -DATA_DIR = CWD.parent.joinpath("app_data") +DATA_DIR = CWD.parent.parent.joinpath("app_data") if PRODUCTION: DATA_DIR = Path("/app/data") @@ -59,6 +62,8 @@ REQUIRED_DIRS = [ ensure_dirs() +LOGGER_FILE = DATA_DIR.joinpath("mealie.log") + # DATABASE ENV SQLITE_FILE = None diff --git a/mealie/core/security.py b/mealie/core/security.py new file mode 100644 index 000000000..f6cc9a805 --- /dev/null +++ b/mealie/core/security.py @@ -0,0 +1,29 @@ +from passlib.context import CryptContext + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + + +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) diff --git a/mealie/db/database.py b/mealie/db/database.py index 04d7abfa4..b74c968c7 100644 --- a/mealie/db/database.py +++ b/mealie/db/database.py @@ -1,10 +1,11 @@ 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 +from db.models.mealplan import MealPlanModel +from db.models.recipe import Category, RecipeModel, Tag +from db.models.settings import SiteSettingsModel +from db.models.theme import SiteThemeModel +from db.models.users import User """ # TODO @@ -55,6 +56,12 @@ class _Themes(BaseDocument): self.sql_model = SiteThemeModel +class _Users(BaseDocument): + def __init__(self) -> None: + self.primary_key = "id" + self.sql_model = User + + class Database: def __init__(self) -> None: self.recipes = _Recipes() @@ -63,6 +70,7 @@ class Database: self.themes = _Themes() self.categories = _Categories() self.tags = _Tags() + self.users = _Users() db = Database() diff --git a/mealie/db/db_base.py b/mealie/db/db_base.py index 40d9144de..6e1204c5c 100644 --- a/mealie/db/db_base.py +++ b/mealie/db/db_base.py @@ -3,7 +3,7 @@ from typing import List from sqlalchemy.orm import load_only from sqlalchemy.orm.session import Session -from db.sql.model_base import SqlAlchemyBase +from db.models.model_base import SqlAlchemyBase class BaseDocument: @@ -108,7 +108,10 @@ class BaseDocument: db_entries = [x.dict() for x in result] if limit == 1: - return db_entries[0] + try: + return db_entries[0] + except IndexError: + return None return db_entries @@ -124,9 +127,8 @@ class BaseDocument: """ new_document = self.sql_model(session=session, **document) session.add(new_document) - return_data = new_document.dict() session.commit() - + return_data = new_document.dict() return return_data def update(self, session: Session, match_value: str, new_data: str) -> dict: diff --git a/mealie/db/db_setup.py b/mealie/db/db_setup.py index aa315a012..e8084321a 100644 --- a/mealie/db/db_setup.py +++ b/mealie/db/db_setup.py @@ -1,7 +1,7 @@ -from app_config import SQLITE_FILE, USE_SQL +from core.config import SQLITE_FILE, USE_SQL from sqlalchemy.orm.session import Session -from db.sql.db_session import sql_global_init +from db.models.db_session import sql_global_init sql_exists = True diff --git a/mealie/db/init_db.py b/mealie/db/init_db.py new file mode 100644 index 000000000..cf44ca5bc --- /dev/null +++ b/mealie/db/init_db.py @@ -0,0 +1,63 @@ +from core.security import get_password_hash +from fastapi.logger import logger +from schema.settings import SiteSettings, Webhooks +from sqlalchemy.orm import Session +from sqlalchemy.orm.session import Session + +from db.database import db +from db.db_setup import create_session + + +def init_db(db: Session = None) -> None: + if not db: + db = create_session() + + default_settings_init(db) + default_theme_init(db) + default_user_init(db) + + db.close() + + +def default_theme_init(session: Session): + default_theme = { + "name": "default", + "colors": { + "primary": "#E58325", + "accent": "#00457A", + "secondary": "#973542", + "success": "#5AB1BB", + "info": "#4990BA", + "warning": "#FF4081", + "error": "#EF5350", + }, + } + + try: + db.themes.create(session, default_theme) + logger.info("Generating default theme...") + except: + logger.info("Default Theme Exists.. skipping generation") + + +def default_settings_init(session: Session): + try: + webhooks = Webhooks() + default_entry = SiteSettings(name="main", webhooks=webhooks) + document = db.settings.create(session, default_entry.dict()) + logger.info(f"Created Site Settings: \n {document}") + except: + pass + + +def default_user_init(session: Session): + default_user = { + "full_name": "Change Me", + "email": "changeme@email.com", + "password": get_password_hash("MyPassword"), + "family": "public", + "admin": True, + } + + logger.info("Generating Default User") + db.users.create(session, default_user) diff --git a/mealie/db/models/_all_models.py b/mealie/db/models/_all_models.py new file mode 100644 index 000000000..e0deb2401 --- /dev/null +++ b/mealie/db/models/_all_models.py @@ -0,0 +1,5 @@ +from db.models.mealplan import * +from db.models.recipe import * +from db.models.settings import * +from db.models.theme import * +from db.models.users import * diff --git a/mealie/db/sql/db_session.py b/mealie/db/models/db_session.py similarity index 88% rename from mealie/db/sql/db_session.py rename to mealie/db/models/db_session.py index 8376cc593..925ed27ef 100644 --- a/mealie/db/sql/db_session.py +++ b/mealie/db/models/db_session.py @@ -1,7 +1,7 @@ from pathlib import Path import sqlalchemy as sa -from db.sql.model_base import SqlAlchemyBase +from 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 db.models._all_models SqlAlchemyBase.metadata.create_all(engine) diff --git a/mealie/db/sql/meal_models.py b/mealie/db/models/mealplan.py similarity index 97% rename from mealie/db/sql/meal_models.py rename to mealie/db/models/mealplan.py index 9001ded78..973227bd3 100644 --- a/mealie/db/sql/meal_models.py +++ b/mealie/db/models/mealplan.py @@ -3,7 +3,7 @@ from typing import List import sqlalchemy as sa import sqlalchemy.orm as orm -from db.sql.model_base import BaseMixins, SqlAlchemyBase +from db.models.model_base import BaseMixins, SqlAlchemyBase class Meal(SqlAlchemyBase): diff --git a/mealie/db/sql/model_base.py b/mealie/db/models/model_base.py similarity index 100% rename from mealie/db/sql/model_base.py rename to mealie/db/models/model_base.py diff --git a/mealie/db/sql/recipe_models.py b/mealie/db/models/recipe.py similarity index 99% rename from mealie/db/sql/recipe_models.py rename to mealie/db/models/recipe.py index cc877e767..a2d341c3b 100644 --- a/mealie/db/sql/recipe_models.py +++ b/mealie/db/models/recipe.py @@ -4,11 +4,11 @@ from typing import List import sqlalchemy as sa import sqlalchemy.orm as orm -from db.sql.model_base import BaseMixins, SqlAlchemyBase +from db.models.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 +from fastapi.logger import logger class ApiExtras(SqlAlchemyBase): diff --git a/mealie/db/sql/settings_models.py b/mealie/db/models/settings.py similarity index 96% rename from mealie/db/sql/settings_models.py rename to mealie/db/models/settings.py index 0eaa67f20..1ba470bd5 100644 --- a/mealie/db/sql/settings_models.py +++ b/mealie/db/models/settings.py @@ -1,7 +1,6 @@ import sqlalchemy as sa import sqlalchemy.orm as orm -from db.sql.model_base import BaseMixins, SqlAlchemyBase -from db.sql.recipe_models import Category +from db.models.model_base import BaseMixins, SqlAlchemyBase class SiteSettingsModel(SqlAlchemyBase, BaseMixins): diff --git a/mealie/db/sql/theme_models.py b/mealie/db/models/theme.py similarity index 96% rename from mealie/db/sql/theme_models.py rename to mealie/db/models/theme.py index c6dca8019..f778545ce 100644 --- a/mealie/db/sql/theme_models.py +++ b/mealie/db/models/theme.py @@ -1,6 +1,6 @@ import sqlalchemy as sa import sqlalchemy.orm as orm -from db.sql.model_base import BaseMixins, SqlAlchemyBase +from db.models.model_base import BaseMixins, SqlAlchemyBase class SiteThemeModel(SqlAlchemyBase): diff --git a/mealie/db/models/users.py b/mealie/db/models/users.py new file mode 100644 index 000000000..ae03d15b9 --- /dev/null +++ b/mealie/db/models/users.py @@ -0,0 +1,44 @@ +from db.models.model_base import BaseMixins, SqlAlchemyBase +from sqlalchemy import Boolean, Column, Integer, String + + +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) + is_active = Column(Boolean(), default=True) + family = Column(String) + admin = Column(Boolean(), default=False) + + def __init__( + self, + session, + full_name, + email, + password, + family="public", + admin=False, + ) -> None: + self.full_name = full_name + self.email = email + self.family = family + self.admin = admin + self.password = password + + def dict(self): + return { + "id": self.id, + "full_name": self.full_name, + "email": self.email, + "admin": self.admin, + "family": self.family, + "password": self.password, + } + + def update(self, full_name, email, family, admin, session=None): + self.full_name = full_name + self.email = email + self.family = family + self.admin = admin diff --git a/mealie/db/sql/_all_models.py b/mealie/db/sql/_all_models.py deleted file mode 100644 index c7df3b3be..000000000 --- a/mealie/db/sql/_all_models.py +++ /dev/null @@ -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 * diff --git a/mealie/routes/__init__.py b/mealie/routes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/mealie/routes/backup_routes.py b/mealie/routes/backup_routes.py index df64c38f7..94f1d93ec 100644 --- a/mealie/routes/backup_routes.py +++ b/mealie/routes/backup_routes.py @@ -1,15 +1,15 @@ import operator import shutil -from app_config import BACKUP_DIR, TEMPLATE_DIR +from core.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 schema.backup import BackupJob, ImportJob, Imports, LocalBackup from services.backups.exports import backup_all from services.backups.imports import ImportDatabase from sqlalchemy.orm.session import Session from starlette.responses import FileResponse -from utils.snackbar import SnackResponse +from schema.snackbar import SnackResponse router = APIRouter(prefix="/api/backups", tags=["Backups"]) diff --git a/mealie/routes/debug_routes.py b/mealie/routes/debug_routes.py index bf7829047..5a3fedd3c 100644 --- a/mealie/routes/debug_routes.py +++ b/mealie/routes/debug_routes.py @@ -1,9 +1,7 @@ import json -from app_config import APP_VERSION, DEBUG_DIR +from core.config import APP_VERSION, DEBUG_DIR, LOGGER_FILE from fastapi import APIRouter -from fastapi.responses import HTMLResponse -from utils.logger import LOGGER_FILE router = APIRouter(prefix="/api/debug", tags=["Debug"]) diff --git a/mealie/routes/deps.py b/mealie/routes/deps.py new file mode 100644 index 000000000..422efc17a --- /dev/null +++ b/mealie/routes/deps.py @@ -0,0 +1,23 @@ +from core.config import SECRET +from db.database import db +from db.db_setup import create_session +from fastapi_login import LoginManager +from sqlalchemy.orm.session import Session + +from schema.user import UserInDB + +manager = LoginManager(SECRET, "/api/auth/token") + + +@manager.user_loader +def query_user(user_email: str, session: Session = None) -> UserInDB: + """ + Get a user from the db + :param user_id: E-Mail of the user + :return: None or the UserInDB object + """ + + session = session if session else create_session() + user = db.users.get(session, user_email, "email") + session.close() + return UserInDB(**user) \ No newline at end of file diff --git a/mealie/routes/meal_routes.py b/mealie/routes/meal_routes.py index 69a716d83..52f71f9dc 100644 --- a/mealie/routes/meal_routes.py +++ b/mealie/routes/meal_routes.py @@ -5,7 +5,7 @@ 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 +from schema.snackbar import SnackResponse router = APIRouter(prefix="/api/meal-plans", tags=["Meal Plan"]) diff --git a/mealie/routes/migration_routes.py b/mealie/routes/migration_routes.py index 19e2c7a5d..cd2b0963f 100644 --- a/mealie/routes/migration_routes.py +++ b/mealie/routes/migration_routes.py @@ -2,14 +2,14 @@ import operator import shutil from typing import List -from app_config import MIGRATION_DIR +from core.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 schema.migration import MigrationFile, Migrations from services.migrations.chowdown import chowdown_migrate as chowdow_migrate from services.migrations.nextcloud import migrate as nextcloud_migrate from sqlalchemy.orm.session import Session -from utils.snackbar import SnackResponse +from schema.snackbar import SnackResponse router = APIRouter(prefix="/api/migrations", tags=["Migration"]) diff --git a/mealie/routes/recipe/all_recipe_routes.py b/mealie/routes/recipe/all_recipe_routes.py index b0106b660..5e3c7ab87 100644 --- a/mealie/routes/recipe/all_recipe_routes.py +++ b/mealie/routes/recipe/all_recipe_routes.py @@ -3,7 +3,7 @@ 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 schema.recipe import AllRecipeRequest from slugify import slugify from sqlalchemy.orm.session import Session diff --git a/mealie/routes/recipe/category_routes.py b/mealie/routes/recipe/category_routes.py index a96eae07d..bbf5b36de 100644 --- a/mealie/routes/recipe/category_routes.py +++ b/mealie/routes/recipe/category_routes.py @@ -1,11 +1,11 @@ from db.database import db from db.db_setup import generate_session from fastapi import APIRouter, Depends -from models.category_models import RecipeCategoryResponse +from schema.category import RecipeCategoryResponse from sqlalchemy.orm.session import Session -from utils.snackbar import SnackResponse +from schema.snackbar import SnackResponse -from utils.snackbar import SnackResponse +from schema.snackbar import SnackResponse router = APIRouter( prefix="/api/categories", @@ -27,7 +27,6 @@ def get_all_recipes_by_category( return db.categories.get(session, category) - @router.delete("/{category}") async def delete_recipe_category( category: str, session: Session = Depends(generate_session) diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index 142c8d3a4..58ace63a8 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -2,12 +2,12 @@ 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 schema.recipe 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 sqlalchemy.orm.session import Session -from utils.snackbar import SnackResponse +from schema.snackbar import SnackResponse router = APIRouter( prefix="/api/recipes", diff --git a/mealie/routes/recipe/tag_routes.py b/mealie/routes/recipe/tag_routes.py index 193046031..f59545e8c 100644 --- a/mealie/routes/recipe/tag_routes.py +++ b/mealie/routes/recipe/tag_routes.py @@ -2,9 +2,9 @@ from db.database import db from db.db_setup import generate_session from fastapi import APIRouter, Depends from sqlalchemy.orm.session import Session -from utils.snackbar import SnackResponse +from schema.snackbar import SnackResponse -from utils.snackbar import SnackResponse +from schema.snackbar import SnackResponse router = APIRouter(tags=["Recipes"]) diff --git a/mealie/routes/setting_routes.py b/mealie/routes/setting_routes.py index a0f6b01cd..b59cd6677 100644 --- a/mealie/routes/setting_routes.py +++ b/mealie/routes/setting_routes.py @@ -1,11 +1,10 @@ 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 schema.settings import SiteSettings from sqlalchemy.orm.session import Session from utils.post_webhooks import post_webhooks -from utils.snackbar import SnackResponse +from schema.snackbar import SnackResponse router = APIRouter(prefix="/api/site-settings", tags=["Settings"]) @@ -17,8 +16,7 @@ def get_main_settings(session: Session = Depends(generate_session)): try: data = db.settings.get(session, "main") except: - default_settings_init(session) - data = db.settings.get(session, "main") + return return data diff --git a/mealie/routes/theme_routes.py b/mealie/routes/theme_routes.py index 1d9646dd4..fa6a26361 100644 --- a/mealie/routes/theme_routes.py +++ b/mealie/routes/theme_routes.py @@ -1,8 +1,8 @@ from db.db_setup import generate_session from fastapi import APIRouter, Depends -from models.theme_models import SiteTheme +from schema.theme import SiteTheme from sqlalchemy.orm.session import Session -from utils.snackbar import SnackResponse +from schema.snackbar import SnackResponse from db.database import db router = APIRouter(prefix="/api", tags=["Themes"]) diff --git a/mealie/routes/users/__init__.py b/mealie/routes/users/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/mealie/routes/users/auth.py b/mealie/routes/users/auth.py new file mode 100644 index 000000000..368f37036 --- /dev/null +++ b/mealie/routes/users/auth.py @@ -0,0 +1,32 @@ +from datetime import timedelta + +from core.security import verify_password +from db.db_setup import generate_session +from fastapi import APIRouter, Depends +from fastapi.security import OAuth2PasswordRequestForm +from fastapi_login.exceptions import InvalidCredentialsException +from routes.deps import manager, query_user +from schema.user import UserInDB +from sqlalchemy.orm.session import Session + +router = APIRouter(prefix="/api/auth", tags=["Auth"]) + + +@router.post("/token") +def token( + data: OAuth2PasswordRequestForm = Depends(), + session: Session = Depends(generate_session), +): + email = data.username + password = data.password + + user: UserInDB = query_user(email, session) + if not user: + raise InvalidCredentialsException # you can also use your own HTTPException + elif not verify_password(password, user.password): + raise InvalidCredentialsException + + access_token = manager.create_access_token( + data=dict(sub=email), expires=timedelta(hours=2) + ) + return {"access_token": access_token, "token_type": "bearer"} diff --git a/mealie/routes/users/crud.py b/mealie/routes/users/crud.py new file mode 100644 index 000000000..5eec6d796 --- /dev/null +++ b/mealie/routes/users/crud.py @@ -0,0 +1,85 @@ +from datetime import timedelta + +from core.security import get_password_hash +from db.database import db +from db.db_setup import generate_session +from fastapi import APIRouter, Depends +from routes.deps import manager, query_user +from schema.user import 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(manager), + session: Session = Depends(generate_session), +): + """ Returns a list of all user in the Database """ + + new_user.password = get_password_hash(new_user.password) + + data = db.users.create(session, new_user.dict()) + return data + + +@router.get("", response_model=list[UserOut]) +async def get_all_users( + current_user: UserInDB = Depends(manager), + 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_user_by_id( + current_user: UserInDB = Depends(manager), + 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(manager), + session: Session = Depends(generate_session), +): + return db.users.get(session, id) + + +@router.put("/{id}") +async def update_user( + id: int, + new_data: UserBase, + current_user: UserInDB = Depends(manager), + session: Session = Depends(generate_session), +): + + if current_user.id == id or current_user.admin: + updated_user = db.users.update(session, id, new_data.dict()) + email = updated_user.get("email") + if current_user.id == id: + access_token = manager.create_access_token( + data=dict(sub=email), expires=timedelta(hours=2) + ) + return {"access_token": access_token, "token_type": "bearer"} + return + + +@router.delete("/{id}") +async def delete_user( + id: int, + current_user: UserInDB = Depends(manager), + session: Session = Depends(generate_session), +): + """ Removes a user from the database. Must be the current user or a super user""" + + if current_user.id == id or current_user.admin: + return db.users.delete(session, id) diff --git a/mealie/routes/users/users.py b/mealie/routes/users/users.py new file mode 100644 index 000000000..309f1d9d3 --- /dev/null +++ b/mealie/routes/users/users.py @@ -0,0 +1,7 @@ +from fastapi import APIRouter +from routes.users import auth, crud + +router = APIRouter() + +router.include_router(auth.router) +router.include_router(crud.router) diff --git a/mealie/models/backup_models.py b/mealie/schema/backup.py similarity index 100% rename from mealie/models/backup_models.py rename to mealie/schema/backup.py diff --git a/mealie/models/category_models.py b/mealie/schema/category.py similarity index 100% rename from mealie/models/category_models.py rename to mealie/schema/category.py diff --git a/mealie/models/meal_models.py b/mealie/schema/meal.py similarity index 100% rename from mealie/models/meal_models.py rename to mealie/schema/meal.py diff --git a/mealie/models/migration_models.py b/mealie/schema/migration.py similarity index 100% rename from mealie/models/migration_models.py rename to mealie/schema/migration.py diff --git a/mealie/models/recipe_models.py b/mealie/schema/recipe.py similarity index 100% rename from mealie/models/recipe_models.py rename to mealie/schema/recipe.py diff --git a/mealie/models/import_models.py b/mealie/schema/restore.py similarity index 100% rename from mealie/models/import_models.py rename to mealie/schema/restore.py diff --git a/mealie/models/settings_models.py b/mealie/schema/settings.py similarity index 100% rename from mealie/models/settings_models.py rename to mealie/schema/settings.py diff --git a/mealie/utils/snackbar.py b/mealie/schema/snackbar.py similarity index 100% rename from mealie/utils/snackbar.py rename to mealie/schema/snackbar.py diff --git a/mealie/models/theme_models.py b/mealie/schema/theme.py similarity index 100% rename from mealie/models/theme_models.py rename to mealie/schema/theme.py diff --git a/mealie/schema/user.py b/mealie/schema/user.py new file mode 100644 index 000000000..e80e9e4ad --- /dev/null +++ b/mealie/schema/user.py @@ -0,0 +1,32 @@ +from typing import Optional + +from fastapi_camelcase import CamelModel + +# from pydantic import EmailStr + + +class UserBase(CamelModel): + full_name: Optional[str] = None + email: str + family: str + admin: bool + + class Config: + schema_extra = { + "fullName": "Change Me", + "email": "changeme@email.com", + "family": "public", + "admin": "false", + } + + +class UserIn(UserBase): + password: str + + +class UserOut(UserBase): + id: int + + +class UserInDB(UserIn, UserOut): + pass diff --git a/mealie/services/backups/exports.py b/mealie/services/backups/exports.py index 8c53e3d70..9076ebced 100644 --- a/mealie/services/backups/exports.py +++ b/mealie/services/backups/exports.py @@ -3,13 +3,13 @@ import shutil from datetime import datetime from pathlib import Path -from app_config import BACKUP_DIR, IMG_DIR, TEMP_DIR, TEMPLATE_DIR +from core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR, TEMPLATE_DIR from db.database import db from db.db_setup import create_session from jinja2 import Template from services.meal_services import MealPlan from services.recipe_services import Recipe -from utils.logger import logger +from fastapi.logger import logger class ExportDatabase: diff --git a/mealie/services/backups/imports.py b/mealie/services/backups/imports.py index 267cba715..2f0944179 100644 --- a/mealie/services/backups/imports.py +++ b/mealie/services/backups/imports.py @@ -4,13 +4,13 @@ import zipfile from pathlib import Path from typing import List -from app_config import BACKUP_DIR, IMG_DIR, TEMP_DIR +from core.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 schema.restore import RecipeImport, SettingsImport, ThemeImport +from schema.theme import SiteTheme from services.recipe_services import Recipe from sqlalchemy.orm.session import Session -from utils.logger import logger +from fastapi.logger import logger class ImportDatabase: diff --git a/mealie/services/image_services.py b/mealie/services/image_services.py index 4e6d542e4..bd73cf301 100644 --- a/mealie/services/image_services.py +++ b/mealie/services/image_services.py @@ -2,8 +2,8 @@ import shutil from pathlib import Path import requests -from app_config import IMG_DIR -from utils.logger import logger +from core.config import IMG_DIR +from fastapi.logger import logger def read_image(recipe_slug: str) -> Path: diff --git a/mealie/services/migrations/chowdown.py b/mealie/services/migrations/chowdown.py index a26215ab9..85bad0554 100644 --- a/mealie/services/migrations/chowdown.py +++ b/mealie/services/migrations/chowdown.py @@ -2,7 +2,7 @@ import shutil from pathlib import Path import yaml -from app_config import IMG_DIR, TEMP_DIR +from core.config import IMG_DIR, TEMP_DIR from services.recipe_services import Recipe from sqlalchemy.orm.session import Session from utils.unzip import unpack_zip diff --git a/mealie/services/migrations/nextcloud.py b/mealie/services/migrations/nextcloud.py index 155d1d120..fe8547a52 100644 --- a/mealie/services/migrations/nextcloud.py +++ b/mealie/services/migrations/nextcloud.py @@ -4,10 +4,10 @@ import shutil import zipfile from pathlib import Path -from app_config import IMG_DIR, MIGRATION_DIR, TEMP_DIR +from core.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 core.config import IMG_DIR, TEMP_DIR def process_selection(selection: Path) -> Path: @@ -81,7 +81,7 @@ def migrate(session, selection: str): successful_imports.append(recipe.name) except: logging.error(f"Failed Nextcloud Import: {dir.name}") - logging.exception('') + logging.exception("") failed_imports.append(dir.name) cleanup() diff --git a/mealie/services/scheduler/scheduled_jobs.py b/mealie/services/scheduler/scheduled_jobs.py index ea93fb202..37e4efeb9 100644 --- a/mealie/services/scheduler/scheduled_jobs.py +++ b/mealie/services/scheduler/scheduled_jobs.py @@ -3,8 +3,8 @@ 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 fastapi.logger import logger +from schema.settings import SiteSettings from db.database import db from utils.post_webhooks import post_webhooks diff --git a/mealie/services/scraper/open_graph.py b/mealie/services/scraper/open_graph.py index 49d1072ef..3b359bcc2 100644 --- a/mealie/services/scraper/open_graph.py +++ b/mealie/services/scraper/open_graph.py @@ -1,7 +1,7 @@ from typing import Tuple import extruct -from app_config import DEBUG_DIR +from core.config import DEBUG_DIR from slugify import slugify from w3lib.html import get_base_url diff --git a/mealie/services/scraper/scraper.py b/mealie/services/scraper/scraper.py index a13ed79e2..516773403 100644 --- a/mealie/services/scraper/scraper.py +++ b/mealie/services/scraper/scraper.py @@ -3,12 +3,12 @@ from typing import List import requests import scrape_schema_recipe -from app_config import DEBUG_DIR +from core.config import DEBUG_DIR +from fastapi.logger import logger from services.image_services import scrape_image from services.recipe_services import Recipe -from services.scraper import open_graph +from services.scraper import open_graph from services.scraper.cleaner import Cleaner -from utils.logger import logger LAST_JSON = DEBUG_DIR.joinpath("last_recipe.json") @@ -25,7 +25,7 @@ def create_from_url(url: str) -> Recipe: """ r = requests.get(url) new_recipe = extract_recipe_from_html(r.text, url) - new_recipe = Cleaner.clean(new_recipe) + new_recipe = Cleaner.clean(new_recipe, url) new_recipe = download_image_for_recipe(new_recipe) recipe = Recipe(**new_recipe) diff --git a/mealie/services/settings_services.py b/mealie/services/settings_services.py deleted file mode 100644 index 62e32bb3d..000000000 --- a/mealie/services/settings_services.py +++ /dev/null @@ -1,17 +0,0 @@ -from db.database import db -from db.db_setup import create_session -from models.settings_models import SiteSettings, Webhooks -from sqlalchemy.orm.session import Session -from utils.logger import logger - - -def default_settings_init(session: Session = None): - if session == None: - session = create_session() - try: - webhooks = Webhooks() - default_entry = SiteSettings(name="main", webhooks=webhooks) - document = db.settings.create(session, default_entry.dict()) - logger.info(f"Created Site Settings: \n {document}") - except: - pass diff --git a/mealie/services/theme_services.py b/mealie/services/theme_services.py deleted file mode 100644 index 12cd9c889..000000000 --- a/mealie/services/theme_services.py +++ /dev/null @@ -1,28 +0,0 @@ -from db.database import db -from db.db_setup import create_session, sql_exists -from utils.logger import logger - - -def default_theme_init(): - default_theme = { - "name": "default", - "colors": { - "primary": "#E58325", - "accent": "#00457A", - "secondary": "#973542", - "success": "#5AB1BB", - "info": "#4990BA", - "warning": "#FF4081", - "error": "#EF5350", - }, - } - session = create_session() - try: - db.themes.create(session, default_theme) - logger.info("Generating default theme...") - except: - logger.info("Default Theme Exists.. skipping generation") - - -if not sql_exists: - default_theme_init() diff --git a/mealie/services/users/__init__.py b/mealie/services/users/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/mealie/tests/conftest.py b/mealie/tests/conftest.py index 4a3c1c763..e73807528 100644 --- a/mealie/tests/conftest.py +++ b/mealie/tests/conftest.py @@ -1,27 +1,27 @@ -from pathlib import Path +import json +import requests from app import app -from app_config import SQLITE_DIR +from core.config import SQLITE_DIR from db.db_setup import generate_session, sql_global_init +from db.init_db import init_db from fastapi.testclient import TestClient from pytest import fixture -from services.settings_services import default_settings_init -from services.theme_services import default_theme_init from tests.test_config import TEST_DATA SQLITE_FILE = SQLITE_DIR.joinpath("test.db") SQLITE_FILE.unlink(missing_ok=True) +TOKEN_URL = "/api/auth/token" TestSessionLocal = sql_global_init(SQLITE_FILE, check_thread=False) +init_db(TestSessionLocal()) def override_get_db(): try: db = TestSessionLocal() - default_theme_init() - default_settings_init() yield db finally: db.close() @@ -31,11 +31,22 @@ def override_get_db(): def api_client(): app.dependency_overrides[generate_session] = override_get_db + yield TestClient(app) - SQLITE_FILE.unlink() + # SQLITE_FILE.unlink() @fixture(scope="session") def test_image(): return TEST_DATA.joinpath("test_image.jpg") + + +@fixture(scope="session") +def token(api_client: requests): + form_data = {"username": "changeme@email.com", "password": "MyPassword"} + response = api_client.post(TOKEN_URL, form_data) + + token = json.loads(response.text).get("access_token") + + return {"Authorization": f"Bearer {token}"} diff --git a/mealie/tests/test_migrations/test_nextcloud.py b/mealie/tests/test_migrations/test_nextcloud.py index 52a251411..b6d363358 100644 --- a/mealie/tests/test_migrations/test_nextcloud.py +++ b/mealie/tests/test_migrations/test_nextcloud.py @@ -1,10 +1,14 @@ from pathlib import Path -from app_config import TEMP_DIR +from core.config import TEMP_DIR import pytest -from app_config import TEMP_DIR +from core.config import TEMP_DIR from services.image_services import IMG_DIR -from services.migrations.nextcloud import (cleanup, import_recipes, prep, - process_selection) +from services.migrations.nextcloud import ( + cleanup, + import_recipes, + prep, + process_selection, +) from services.recipe_services import Recipe from tests.test_config import TEST_NEXTCLOUD_DIR diff --git a/mealie/tests/test_routes/test_migration_routes.py b/mealie/tests/test_routes/test_migration_routes.py index 2bb35ec93..4efa280b7 100644 --- a/mealie/tests/test_routes/test_migration_routes.py +++ b/mealie/tests/test_routes/test_migration_routes.py @@ -2,7 +2,7 @@ import json import shutil import pytest -from app_config import MIGRATION_DIR +from core.config import MIGRATION_DIR from tests.test_config import TEST_CHOWDOWN_DIR, TEST_NEXTCLOUD_DIR from tests.utils.routes import MIGRATIONS_PREFIX, RECIPES_PREFIX diff --git a/mealie/tests/test_routes/test_settings_routes.py b/mealie/tests/test_routes/test_settings_routes.py index 56ccae369..fe4bbdf65 100644 --- a/mealie/tests/test_routes/test_settings_routes.py +++ b/mealie/tests/test_routes/test_settings_routes.py @@ -34,8 +34,6 @@ def default_theme(api_client): }, } - api_client.post(THEMES_CREATE, json=default_theme) - return default_theme diff --git a/mealie/tests/test_routes/test_user_routes.py b/mealie/tests/test_routes/test_user_routes.py new file mode 100644 index 000000000..02a542d0e --- /dev/null +++ b/mealie/tests/test_routes/test_user_routes.py @@ -0,0 +1,93 @@ +import json + +import requests +from pytest import fixture + +BASE = "/api/users" +TOKEN_URL = "/api/auth/token" + + + + + +@fixture(scope="session") +def default_user(): + return { + "id": 1, + "fullName": "Change Me", + "email": "changeme@email.com", + "family": "public", + "admin": True + } + + +@fixture(scope="session") +def new_user(): + return { + "id": 2, + "fullName": "My New User", + "email": "newuser@email.com", + "family": "public", + "admin": False + } + + +def test_superuser_login(api_client: requests): + form_data = {"username": "changeme@email.com", "password": "MyPassword"} + response = api_client.post(TOKEN_URL, form_data) + + assert response.status_code == 200 + token = json.loads(response.text).get("access_token") + + return {"Authorization": f"Bearer {token}"} + + +def test_init_superuser(api_client: requests, token, default_user): + response = api_client.get(f"{BASE}/1", headers=token) + assert response.status_code == 200 + + assert json.loads(response.text) == default_user + + +def test_create_user(api_client: requests, token, new_user): + create_data = { + "fullName": "My New User", + "email": "newuser@email.com", + "password": "MyStrongPassword", + "family": "public", + "admin": False + } + + response = api_client.post(f"{BASE}", json=create_data, headers=token) + + assert response.status_code == 201 + assert json.loads(response.text) == new_user + assert True + + +def test_get_all_users(api_client: requests, token, new_user, default_user): + response = api_client.get(f"{BASE}", headers=token) + + assert response.status_code == 200 + + assert json.loads(response.text) == [default_user, new_user] + + +def test_update_user(api_client: requests, token): + update_data = { + "id": 1, + "fullName": "Updated Name", + "email": "updated@email.com", + "family": "public", + "admin": True + } + response = api_client.put(f"{BASE}/1", headers=token, json=update_data) + + assert response.status_code == 200 + assert json.loads(response.text).get("access_token") + + +def test_delete_user(api_client: requests, token): + response = api_client.delete(f"{BASE}/2", headers=token) + + assert response.status_code == 200 diff --git a/mealie/tests/test_services/test_migrations/test_nextcloud.py b/mealie/tests/test_services/test_migrations/test_nextcloud.py index 52a251411..b6d363358 100644 --- a/mealie/tests/test_services/test_migrations/test_nextcloud.py +++ b/mealie/tests/test_services/test_migrations/test_nextcloud.py @@ -1,10 +1,14 @@ from pathlib import Path -from app_config import TEMP_DIR +from core.config import TEMP_DIR import pytest -from app_config import TEMP_DIR +from core.config import TEMP_DIR from services.image_services import IMG_DIR -from services.migrations.nextcloud import (cleanup, import_recipes, prep, - process_selection) +from services.migrations.nextcloud import ( + cleanup, + import_recipes, + prep, + process_selection, +) from services.recipe_services import Recipe from tests.test_config import TEST_NEXTCLOUD_DIR diff --git a/mealie/utils/api_docs.py b/mealie/utils/api_docs.py index 356789789..d0abc39aa 100644 --- a/mealie/utils/api_docs.py +++ b/mealie/utils/api_docs.py @@ -1,6 +1,6 @@ import json -from app_config import DATA_DIR +from core.config import DATA_DIR """Script to export the ReDoc documentation page into a standalone HTML file.""" @@ -37,6 +37,3 @@ HTML_PATH = DATA_DIR.parent.joinpath("docs/docs/html/api.html") def generate_api_docs(app): with open(HTML_PATH, "w") as fd: print(HTML_TEMPLATE % json.dumps(app.openapi()), file=fd) - - - diff --git a/mealie/utils/global_scheduler.py b/mealie/utils/global_scheduler.py deleted file mode 100644 index 77d574dd0..000000000 --- a/mealie/utils/global_scheduler.py +++ /dev/null @@ -1,11 +0,0 @@ -from services.scheduler_services import Scheduler - - -def start_scheduler(): - global scheduler - scheduler = Scheduler() - scheduler.startup_scheduler() - return scheduler - - -scheduler = start_scheduler() diff --git a/mealie/utils/logger.py b/mealie/utils/logger.py deleted file mode 100644 index 03fa87933..000000000 --- a/mealie/utils/logger.py +++ /dev/null @@ -1,25 +0,0 @@ -import logging -from pathlib import Path - -from app_config import DATA_DIR - -LOGGER_LEVEL = "INFO" -CWD = Path(__file__).parent -LOGGER_FILE = DATA_DIR.joinpath("mealie.log") - - -logging.basicConfig( - level=LOGGER_LEVEL, - format="%(asctime)s %(levelname)s: %(message)s", - datefmt="%d-%b-%y %H:%M:%S", -) - -logger = logging.getLogger(__name__) - -""" Logging Cheat Sheet -logger.debug("this is a debugging message") -logger.info("this is an informational message") -logger.warning("this is a warning message") -logger.error("this is an error message") -logger.critical("this is a critical message") -""" diff --git a/mealie/utils/post_webhooks.py b/mealie/utils/post_webhooks.py index 90520c51a..9bc475039 100644 --- a/mealie/utils/post_webhooks.py +++ b/mealie/utils/post_webhooks.py @@ -3,7 +3,7 @@ import json import requests from db.database import db from db.db_setup import create_session -from models.settings_models import SiteSettings +from schema.settings import SiteSettings from services.meal_services import MealPlan from services.recipe_services import Recipe diff --git a/mealie/utils/startup.py b/mealie/utils/startup.py deleted file mode 100644 index b97811693..000000000 --- a/mealie/utils/startup.py +++ /dev/null @@ -1,13 +0,0 @@ -from pathlib import Path - -from services.settings_services import default_theme_init - -CWD = Path(__file__).parent - - -def post_start(): - default_theme_init() - - -if __name__ == "__main__": - pass diff --git a/mealie/utils/unzip.py b/mealie/utils/unzip.py index b8e53767b..930da7365 100644 --- a/mealie/utils/unzip.py +++ b/mealie/utils/unzip.py @@ -2,7 +2,7 @@ import tempfile import zipfile from pathlib import Path -from app_config import TEMP_DIR +from core.config import TEMP_DIR def unpack_zip(selection: Path) -> tempfile.TemporaryDirectory: diff --git a/poetry.lock b/poetry.lock index 3b5e65cb4..0e8fef8cf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -83,6 +83,22 @@ docs = ["furo", "sphinx", "zope.interface"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +[[package]] +name = "bcrypt" +version = "3.2.0" +description = "Modern password hashing for your software and your servers" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.1" +six = ">=1.4.1" + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + [[package]] name = "beautifulsoup4" version = "4.9.3" @@ -128,6 +144,17 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "cffi" +version = "1.14.5" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + [[package]] name = "chardet" version = "4.0.0" @@ -211,6 +238,31 @@ dev = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,< doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=6.1.4,<7.0.0)", "markdown-include (>=0.5.1,<0.6.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.2.0)", "typer-cli (>=0.0.9,<0.0.10)", "pyyaml (>=5.3.1,<6.0.0)"] test = ["pytest (==5.4.3)", "pytest-cov (==2.10.0)", "pytest-asyncio (>=0.14.0,<0.15.0)", "mypy (==0.790)", "flake8 (>=3.8.3,<4.0.0)", "black (==20.8b1)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.15.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.4.0)", "orjson (>=3.2.1,<4.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.6.0)", "flask (>=1.1.2,<2.0.0)"] +[[package]] +name = "fastapi-camelcase" +version = "1.0.2" +description = "Package provides an easy way to have camelcase request/response bodies for Pydantic" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pydantic = "*" +pyhumps = "*" + +[[package]] +name = "fastapi-login" +version = "1.5.3" +description = "" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +fastapi = "*" +passlib = "*" +pyjwt = "*" + [[package]] name = "h11" version = "0.12.0" @@ -267,18 +319,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "importlib-resources" -version = "5.1.0" -description = "Read resources from Python packages" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] - [[package]] name = "iniconfig" version = "1.1.1" @@ -403,6 +443,20 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pyparsing = ">=2.0.2" +[[package]] +name = "passlib" +version = "1.7.4" +description = "comprehensive password hashing framework supporting over 30 schemes" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +argon2 = ["argon2-cffi (>=18.2.0)"] +bcrypt = ["bcrypt (>=3.1.0)"] +build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.1)"] +totp = ["cryptography"] + [[package]] name = "pathspec" version = "0.8.1" @@ -430,6 +484,14 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "pydantic" version = "1.7.3" @@ -443,6 +505,28 @@ dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] typing_extensions = ["typing-extensions (>=3.7.2)"] +[[package]] +name = "pyhumps" +version = "1.6.1" +description = "🐫 Convert strings (and dictionary keys) between snake case, camel case and pascal case in Python. Inspired by Humps for Node" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyjwt" +version = "2.0.1" +description = "JSON Web Token implementation in Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +crypto = ["cryptography (>=3.3.1,<4.0.0)"] +dev = ["sphinx", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.3.1,<4.0.0)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "mypy", "pre-commit"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"] + [[package]] name = "pylint" version = "2.6.0" @@ -632,7 +716,6 @@ python-versions = "*" [package.dependencies] extruct = "*" -importlib-resources = {version = "*", markers = "python_version < \"3.9\""} isodate = ">=0.5.1" requests = "*" validators = ">=0.12.4" @@ -742,7 +825,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "uvicorn" -version = "0.13.3" +version = "0.13.4" description = "The lightning-fast ASGI server." category = "main" optional = false @@ -755,12 +838,12 @@ h11 = ">=0.8" httptools = {version = ">=0.1.0,<0.2.0", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} PyYAML = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} -uvloop = {version = ">=0.14.0", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} -watchgod = {version = ">=0.6,<0.7", optional = true, markers = "extra == \"standard\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchgod = {version = ">=0.6", optional = true, markers = "extra == \"standard\""} websockets = {version = ">=8.0.0,<9.0.0", optional = true, markers = "extra == \"standard\""} [package.extras] -standard = ["websockets (>=8.0.0,<9.0.0)", "watchgod (>=0.6,<0.7)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "httptools (>=0.1.0,<0.2.0)", "uvloop (>=0.14.0)", "colorama (>=0.4)"] +standard = ["websockets (>=8.0.0,<9.0.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "httptools (>=0.1.0,<0.2.0)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] [[package]] name = "uvloop" @@ -830,8 +913,8 @@ python-versions = "*" [metadata] lock-version = "1.1" -python-versions = "^3.8" -content-hash = "fbe2a3d2885fcc24abe5a285a961f968ea32aa22182f88f44a7c9dd624c968b5" +python-versions = "^3.9" +content-hash = "182496243ce59b60506b646a0a521d0186a7ef36009c5212276807e13e20d8a3" [metadata.files] aiofiles = [ @@ -862,6 +945,15 @@ attrs = [ {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] +bcrypt = [ + {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"}, + {file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"}, + {file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"}, + {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, +] beautifulsoup4 = [ {file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"}, {file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"}, @@ -874,6 +966,45 @@ certifi = [ {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, ] +cffi = [ + {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, + {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, + {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, + {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, + {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, + {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, + {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, + {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, + {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, + {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, + {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, + {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, + {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, + {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, + {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, + {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, + {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, + {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, + {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, +] chardet = [ {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, @@ -949,6 +1080,13 @@ fastapi = [ {file = "fastapi-0.63.0-py3-none-any.whl", hash = "sha256:98d8ea9591d8512fdadf255d2a8fa56515cdd8624dca4af369da73727409508e"}, {file = "fastapi-0.63.0.tar.gz", hash = "sha256:63c4592f5ef3edf30afa9a44fa7c6b7ccb20e0d3f68cd9eba07b44d552058dcb"}, ] +fastapi-camelcase = [ + {file = "fastapi_camelcase-1.0.2.tar.gz", hash = "sha256:1d852149f6c9e5bb8002839a1e024050af917f1944b9d108d56468d64c6da279"}, +] +fastapi-login = [ + {file = "fastapi-login-1.5.3.tar.gz", hash = "sha256:8e8ef710f1b7107e81d00e205779e73e17be35d5a91d11685ff72f323898e93b"}, + {file = "fastapi_login-1.5.3-py3-none-any.whl", hash = "sha256:6c83b74bdb45c34ec0aab22000a7951df96c5d011f02a99a46ca4b2be6b1263c"}, +] h11 = [ {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, @@ -979,10 +1117,6 @@ idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] -importlib-resources = [ - {file = "importlib_resources-5.1.0-py3-none-any.whl", hash = "sha256:885b8eae589179f661c909d699a546cf10d83692553e34dca1bf5eb06f7f6217"}, - {file = "importlib_resources-5.1.0.tar.gz", hash = "sha256:bfdad047bce441405a49cf8eb48ddce5e56c696e185f59147a8b79e75e9e6380"}, -] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, @@ -1083,20 +1217,39 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] mccabe = [ @@ -1114,6 +1267,10 @@ packaging = [ {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, ] +passlib = [ + {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, + {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, +] pathspec = [ {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, @@ -1126,6 +1283,10 @@ py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, ] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] pydantic = [ {file = "pydantic-1.7.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c59ea046aea25be14dc22d69c97bee629e6d48d2b2ecb724d7fe8806bf5f61cd"}, {file = "pydantic-1.7.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a4143c8d0c456a093387b96e0f5ee941a950992904d88bc816b4f0e72c9a0009"}, @@ -1150,6 +1311,14 @@ pydantic = [ {file = "pydantic-1.7.3-py3-none-any.whl", hash = "sha256:38be427ea01a78206bcaf9a56f835784afcba9e5b88fbdce33bbbfbcd7841229"}, {file = "pydantic-1.7.3.tar.gz", hash = "sha256:213125b7e9e64713d16d988d10997dabc6a1f73f3991e1ff8e35ebb1409c7dc9"}, ] +pyhumps = [ + {file = "pyhumps-1.6.1-py3-none-any.whl", hash = "sha256:58b367b73c57b64e32d211dc769addabd68ff6db07ce64b2e6565f7d5a12291f"}, + {file = "pyhumps-1.6.1.tar.gz", hash = "sha256:01612603c5ad73a407299d806d30708a3935052276fdd93776953bccc0724e0a"}, +] +pyjwt = [ + {file = "PyJWT-2.0.1-py3-none-any.whl", hash = "sha256:b70b15f89dc69b993d8a8d32c299032d5355c82f9b5b7e851d1a6d706dffe847"}, + {file = "PyJWT-2.0.1.tar.gz", hash = "sha256:a5c70a06e1f33d81ef25eecd50d50bd30e34de1ca8b2b9fa3fe0daaabcf69bf7"}, +] pylint = [ {file = "pylint-2.6.0-py3-none-any.whl", hash = "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f"}, {file = "pylint-2.6.0.tar.gz", hash = "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210"}, @@ -1371,8 +1540,8 @@ urllib3 = [ {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, ] uvicorn = [ - {file = "uvicorn-0.13.3-py3-none-any.whl", hash = "sha256:1079c50a06f6338095b4f203e7861dbff318dde5f22f3a324fc6e94c7654164c"}, - {file = "uvicorn-0.13.3.tar.gz", hash = "sha256:ef1e0bb5f7941c6fe324e06443ddac0331e1632a776175f87891c7bd02694355"}, + {file = "uvicorn-0.13.4-py3-none-any.whl", hash = "sha256:7587f7b08bd1efd2b9bad809a3d333e972f1d11af8a5e52a9371ee3a5de71524"}, + {file = "uvicorn-0.13.4.tar.gz", hash = "sha256:3292251b3c7978e8e4a7868f4baf7f7f7bb7e40c759ecc125c37e99cdea34202"}, ] uvloop = [ {file = "uvloop-0.14.0-cp35-cp35m-macosx_10_11_x86_64.whl", hash = "sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd"}, diff --git a/pyproject.toml b/pyproject.toml index 9be84a044..de3809ac0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ license = "MIT" start = "app:app" [tool.poetry.dependencies] -python = "^3.8" +python = "^3.9" aiofiles = "0.5.0" aniso8601 = "7.0.0" appdirs = "1.4.4" @@ -25,6 +25,9 @@ PyYAML = "^5.3.1" extruct = "^0.12.0" scrape-schema-recipe = "^0.1.3" python-multipart = "^0.0.5" +fastapi-login = "^1.5.3" +bcrypt = "^3.2.0" +fastapi-camelcase = "^1.0.2" [tool.poetry.dev-dependencies] pylint = "^2.6.0"