mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-30 17:53:31 -04:00 
			
		
		
		
	More localization (#358)
* Translate missing items on About page * Localize import summary dialog * Make site menu translation reactive * Localize import options * Include semi colon in string * Move API texts to frontend + better status codes * Provide feedback to user when no meal is planned * Fix API tests after latest rework * Add warning for API changes in changelog * Refactor API texts handling * Refactor API texts handling #2 * Better API feedback * Rearrange strings hierarchy * Add messages upon recipe updated * Fix 'recipe effected' typo * Remove snackbar usage in backend * Translate toolbox * Provide feedback for tags CRUD * Fix messed up merge * Translate sign-up form * Better feedback for sign-up CRUD * Refactor log-in API texts handling * No error message when user is not authenticated * Remove unimportant console log
This commit is contained in:
		| @@ -17,6 +17,8 @@ | |||||||
| - Fixes #281 - Slow Handling of Large Sets of Recipes | - Fixes #281 - Slow Handling of Large Sets of Recipes | ||||||
|  |  | ||||||
| ## Features and Improvements | ## Features and Improvements | ||||||
|  | - 'Dinner this week' shows a warning when no meal is planned yet | ||||||
|  | - 'Dinner today' shows a warning when no meal is planned yet | ||||||
|  |  | ||||||
| ### General | ### General | ||||||
| - New Toolbox Page! | - New Toolbox Page! | ||||||
| @@ -38,3 +40,6 @@ | |||||||
| ### Behind the Scenes | ### Behind the Scenes | ||||||
| - Unified Sidebar Components | - Unified Sidebar Components | ||||||
| - Refactor UI components to fit Vue best practices (WIP) | - Refactor UI components to fit Vue best practices (WIP) | ||||||
|  | - The API returns more consistent status codes | ||||||
|  | - The API returns error code instead of error text when appropriate  | ||||||
|  |   - ⚠️ May cause side-effects if you were directly consuming the API | ||||||
| @@ -1,75 +1,57 @@ | |||||||
| const baseURL = "/api/"; | const baseURL = "/api/"; | ||||||
| import axios from "axios"; | import axios from "axios"; | ||||||
| import utils from "@/utils"; |  | ||||||
| import { store } from "../store"; | import { store } from "../store"; | ||||||
|  | import utils from "@/utils"; | ||||||
|  |  | ||||||
| axios.defaults.headers.common[ | axios.defaults.headers.common[ | ||||||
|   "Authorization" |   "Authorization" | ||||||
| ] = `Bearer ${store.getters.getToken}`; | ] = `Bearer ${store.getters.getToken}`; | ||||||
|  |  | ||||||
| function processResponse(response) { | function handleError(error, getText) { | ||||||
|   try { |   if(getText) { | ||||||
|     utils.notify.show(response.data.snackbar.text, response.data.snackbar.type); |     utils.notify.error(getText(error.response)); | ||||||
|   } catch (err) { |  | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|  |   return false; | ||||||
|  | } | ||||||
|  | function handleResponse(response, getText) { | ||||||
|  |   if(response && getText) { | ||||||
|  |     const successText = getText(response); | ||||||
|  |     utils.notify.success(successText); | ||||||
|  |   } | ||||||
|  |   return response; | ||||||
|  | } | ||||||
|  |  | ||||||
|   return; | function defaultErrorText(response) { | ||||||
|  |   return response.statusText; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function defaultSuccessText(response) { | ||||||
|  |   return response.statusText; | ||||||
| } | } | ||||||
|  |  | ||||||
| const apiReq = { | const apiReq = { | ||||||
|   post: async function(url, data) { |   post: async function(url, data, getErrorText = defaultErrorText, getSuccessText) { | ||||||
|     let response = await axios.post(url, data).catch(function(error) { |     const response = await axios.post(url, data).catch(function(error) { handleError(error, getErrorText) }); | ||||||
|       if (error.response) { |     return handleResponse(response, getSuccessText); | ||||||
|         processResponse(error.response); |  | ||||||
|         return error.response; |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|     processResponse(response); |  | ||||||
|     return response; |  | ||||||
|   },  |   },  | ||||||
|  |  | ||||||
|   put: async function(url, data) { |   put: async function(url, data, getErrorText = defaultErrorText, getSuccessText) { | ||||||
|     let response = await axios.put(url, data).catch(function(error) { |     const response = await axios.put(url, data).catch(function(error) { handleError(error, getErrorText) }); | ||||||
|       if (error.response) { |     return handleResponse(response, getSuccessText); | ||||||
|         processResponse(error.response); |  | ||||||
|         return response; |  | ||||||
|       } else return; |  | ||||||
|     }); |  | ||||||
|     processResponse(response); |  | ||||||
|     return response; |  | ||||||
|   }, |  | ||||||
|   patch: async function(url, data) { |  | ||||||
|     let response = await axios.patch(url, data).catch(function(error) { |  | ||||||
|       if (error.response) { |  | ||||||
|         processResponse(error.response); |  | ||||||
|         return response; |  | ||||||
|       } else return; |  | ||||||
|     }); |  | ||||||
|     processResponse(response); |  | ||||||
|     return response; |  | ||||||
|   }, |   }, | ||||||
|    |    | ||||||
|   get: async function(url, data) { |   patch: async function(url, data, getErrorText = defaultErrorText, getSuccessText) { | ||||||
|     let response = await axios.get(url, data).catch(function(error) { |     const response = await axios.patch(url, data).catch(function(error) { handleError(error, getErrorText) }); | ||||||
|       if (error.response) { |     return handleResponse(response, getSuccessText); | ||||||
|         processResponse(error.response); |  | ||||||
|         return response; |  | ||||||
|       } else return; |  | ||||||
|     }); |  | ||||||
|     processResponse(response); |  | ||||||
|     return response; |  | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   delete: async function(url, data) { |   get: function(url, data, getErrorText = defaultErrorText) { | ||||||
|     let response = await axios.delete(url, data).catch(function(error) { |     return axios.get(url, data).catch(function(error) { handleError(error, getErrorText) }); | ||||||
|       if (error.response) { |   }, | ||||||
|         processResponse(error.response); |  | ||||||
|         return response; |   delete: async function(url, data, getErrorText = defaultErrorText, getSuccessText = defaultSuccessText ) { | ||||||
|       } |     const response = await axios.delete(url, data).catch( function(error) { handleError(error, getErrorText) } ); | ||||||
|     }); |     return handleResponse(response, getSuccessText); | ||||||
|     processResponse(response); |  | ||||||
|     return response; |  | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async download(url) { |   async download(url) { | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import { baseURL } from "./api-utils"; | import { baseURL } from "./api-utils"; | ||||||
| import { apiReq } from "./api-utils"; | import { apiReq } from "./api-utils"; | ||||||
| import { store } from "@/store"; | import { store } from "@/store"; | ||||||
|  | import i18n from '@/i18n.js'; | ||||||
|  |  | ||||||
| const backupBase = baseURL + "backups/"; | const backupBase = baseURL + "backups/"; | ||||||
|  |  | ||||||
| @@ -40,7 +41,12 @@ export const backupAPI = { | |||||||
|    * @param {string} fileName |    * @param {string} fileName | ||||||
|    */ |    */ | ||||||
|   async delete(fileName) { |   async delete(fileName) { | ||||||
|     await apiReq.delete(backupURLs.deleteBackup(fileName)); |     return apiReq.delete( | ||||||
|  |       backupURLs.deleteBackup(fileName), | ||||||
|  |       null, | ||||||
|  |       function() { return i18n.t('settings.backup.unable-to-delete-backup'); }, | ||||||
|  |       function() { return i18n.t('settings.backup.backup-deleted'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|   /** |   /** | ||||||
|    * Creates a backup on the serve given a set of options |    * Creates a backup on the serve given a set of options | ||||||
| @@ -48,8 +54,12 @@ export const backupAPI = { | |||||||
|    * @returns |    * @returns | ||||||
|    */ |    */ | ||||||
|   async create(options) { |   async create(options) { | ||||||
|     let response = apiReq.post(backupURLs.createBackup, options); |     return apiReq.post( | ||||||
|     return response; |       backupURLs.createBackup,  | ||||||
|  |       options, | ||||||
|  |       function() { return i18n.t('settings.backup.error-creating-backup-see-log-file'); }, | ||||||
|  |       function(response) { return i18n.t('settings.backup.backup-created-at-response-export_path', {path: response.data.export_path}); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|   /** |   /** | ||||||
|    * Downloads a file from the server. I don't actually think this is used? |    * Downloads a file from the server. I don't actually think this is used? | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import { baseURL } from "./api-utils"; | import { baseURL } from "./api-utils"; | ||||||
| import { apiReq } from "./api-utils"; | import { apiReq } from "./api-utils"; | ||||||
| import { store } from "@/store"; | import { store } from "@/store"; | ||||||
|  | import i18n from '@/i18n.js'; | ||||||
|  |  | ||||||
| const prefix = baseURL + "categories"; | const prefix = baseURL + "categories"; | ||||||
|  |  | ||||||
| @@ -22,29 +23,44 @@ export const categoryAPI = { | |||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|   async create(name) { |   async create(name) { | ||||||
|     let response = await apiReq.post(categoryURLs.getAll, { name: name }); |     const response = await apiReq.post( | ||||||
|     store.dispatch("requestCategories"); |       categoryURLs.getAll,  | ||||||
|     return response.data; |       { name: name }, | ||||||
|  |       function() { return i18n.t('category.category-creation-failed'); }, | ||||||
|  |       function() { return i18n.t('category.category-created'); } | ||||||
|  |     ); | ||||||
|  |     if(response) { | ||||||
|  |       store.dispatch("requestCategories"); | ||||||
|  |       return response.data; | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
|   async getRecipesInCategory(category) { |   async getRecipesInCategory(category) { | ||||||
|     let response = await apiReq.get(categoryURLs.getCategory(category)); |     let response = await apiReq.get(categoryURLs.getCategory(category)); | ||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|   async update(name, newName, overrideRequest = false) { |   async update(name, newName, overrideRequest = false) { | ||||||
|     let response = await apiReq.put(categoryURLs.updateCategory(name), { |     const response = await apiReq.put( | ||||||
|       name: newName, |       categoryURLs.updateCategory(name),  | ||||||
|     }); |       { name: newName }, | ||||||
|     if (!overrideRequest) { |       function() { return i18n.t('category.category-update-failed'); }, | ||||||
|  |       function() { return i18n.t('category.category-updated'); } | ||||||
|  |     ); | ||||||
|  |     if (response && !overrideRequest) { | ||||||
|       store.dispatch("requestCategories"); |       store.dispatch("requestCategories"); | ||||||
|  |       return response.data; | ||||||
|     } |     } | ||||||
|     return response.data; |  | ||||||
|   }, |   }, | ||||||
|   async delete(category, overrideRequest = false) { |   async delete(category, overrideRequest = false) { | ||||||
|     let response = await apiReq.delete(categoryURLs.deleteCategory(category)); |     const response = await apiReq.delete( | ||||||
|     if (!overrideRequest) { |       categoryURLs.deleteCategory(category), | ||||||
|  |       null, | ||||||
|  |       function() { return i18n.t('category.category-deletion-failed'); }, | ||||||
|  |       function() { return i18n.t('category.category-deleted'); } | ||||||
|  |     ); | ||||||
|  |     if (response && !overrideRequest) { | ||||||
|       store.dispatch("requestCategories"); |       store.dispatch("requestCategories"); | ||||||
|     } |     } | ||||||
|     return response.data; |     return response; | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -68,28 +84,48 @@ export const tagAPI = { | |||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|   async create(name) { |   async create(name) { | ||||||
|     let response = await apiReq.post(tagURLs.getAll, { name: name }); |     const response = await apiReq.post( | ||||||
|     store.dispatch("requestTags"); |       tagURLs.getAll,  | ||||||
|     return response.data; |       { name: name }, | ||||||
|  |       function() { return i18n.t('tag.tag-creation-failed'); }, | ||||||
|  |       function() { return i18n.t('tag.tag-created'); } | ||||||
|  |     ); | ||||||
|  |     if(response) { | ||||||
|  |       store.dispatch("requestTags"); | ||||||
|  |       return response.data; | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
|   async getRecipesInTag(tag) { |   async getRecipesInTag(tag) { | ||||||
|     let response = await apiReq.get(tagURLs.getTag(tag)); |     let response = await apiReq.get(tagURLs.getTag(tag)); | ||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|   async update(name, newName, overrideRequest = false) { |   async update(name, newName, overrideRequest = false) { | ||||||
|     let response = await apiReq.put(tagURLs.updateTag(name), { name: newName }); |     const response = await apiReq.put( | ||||||
|  |       tagURLs.updateTag(name),  | ||||||
|  |       { name: newName }, | ||||||
|  |       function() { return i18n.t('tag.tag-update-failed'); }, | ||||||
|  |       function() { return i18n.t('tag.tag-updated'); } | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     if (!overrideRequest) { |     if(response) { | ||||||
|       store.dispatch("requestTags"); |       if (!overrideRequest) { | ||||||
|  |         store.dispatch("requestTags"); | ||||||
|  |       } | ||||||
|  |       return response.data; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return response.data; |  | ||||||
|   }, |   }, | ||||||
|   async delete(tag, overrideRequest = false) { |   async delete(tag, overrideRequest = false) { | ||||||
|     let response = await apiReq.delete(tagURLs.deleteTag(tag)); |     const response = await apiReq.delete( | ||||||
|     if (!overrideRequest) { |       tagURLs.deleteTag(tag), | ||||||
|       store.dispatch("requestTags"); |       null, | ||||||
|  |       function() { return i18n.t('tag.tag-deletion-failed'); }, | ||||||
|  |       function() { return i18n.t('tag.tag-deleted'); } | ||||||
|  |     ); | ||||||
|  |     if(response) { | ||||||
|  |       if (!overrideRequest) { | ||||||
|  |         store.dispatch("requestTags"); | ||||||
|  |       } | ||||||
|  |       return response.data; | ||||||
|     } |     } | ||||||
|     return response.data; |  | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import { baseURL } from "./api-utils"; | import { baseURL } from "./api-utils"; | ||||||
| import { apiReq } from "./api-utils"; | import { apiReq } from "./api-utils"; | ||||||
|  | import i18n from '@/i18n.js'; | ||||||
| const groupPrefix = baseURL + "groups"; | const groupPrefix = baseURL + "groups"; | ||||||
|  |  | ||||||
| const groupsURLs = { | const groupsURLs = { | ||||||
| @@ -10,25 +11,58 @@ const groupsURLs = { | |||||||
|   update: id => `${groupPrefix}/${id}`, |   update: id => `${groupPrefix}/${id}`, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | function deleteErrorText(response) { | ||||||
|  |   switch(response.data.detail) { | ||||||
|  |     case 'GROUP_WITH_USERS': | ||||||
|  |       return i18n.t('group.cannot-delete-group-with-users'); | ||||||
|  |        | ||||||
|  |     case 'GROUP_NOT_FOUND': | ||||||
|  |       return i18n.t('group.group-not-found'); | ||||||
|  |        | ||||||
|  |     case 'DEFAULT_GROUP': | ||||||
|  |       return i18n.t('group.cannot-delete-default-group'); | ||||||
|  |  | ||||||
|  |     default: | ||||||
|  |       return i18n.t('group.group-deletion-failed'); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| export const groupAPI = { | export const groupAPI = { | ||||||
|   async allGroups() { |   async allGroups() { | ||||||
|     let response = await apiReq.get(groupsURLs.groups); |     let response = await apiReq.get(groupsURLs.groups); | ||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|   async create(name) { |   create(name) { | ||||||
|     let response = await apiReq.post(groupsURLs.create, { name: name }); |     return apiReq.post( | ||||||
|     return response.data; |       groupsURLs.create, | ||||||
|  |       { name: name }, | ||||||
|  |       function() { return i18n.t('group.user-group-creation-failed'); }, | ||||||
|  |       function() { return i18n.t('group.user-group-created'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|   async delete(id) { |   delete(id) { | ||||||
|     let response = await apiReq.delete(groupsURLs.delete(id)); |      return apiReq.delete( | ||||||
|     return response.data; |        groupsURLs.delete(id),  | ||||||
|  |        null,  | ||||||
|  |        deleteErrorText, | ||||||
|  |        function() { return i18n.t('group.group-deleted'); } | ||||||
|  |      ); | ||||||
|   }, |   }, | ||||||
|   async current() { |   async current() { | ||||||
|     let response = await apiReq.get(groupsURLs.current); |     const response = await apiReq.get( | ||||||
|     return response.data; |       groupsURLs.current, | ||||||
|  |       null, | ||||||
|  |       null); | ||||||
|  |     if(response) { | ||||||
|  |       return response.data; | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
|   async update(data) { |   update(data) { | ||||||
|     let response = await apiReq.put(groupsURLs.update(data.id), data); |     return apiReq.put( | ||||||
|     return response.data; |       groupsURLs.update(data.id),  | ||||||
|  |       data,  | ||||||
|  |       function() { return i18n.t('group.error-updating-group'); }, | ||||||
|  |       function() { return i18n.t('settings.group-settings-updated'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import { baseURL } from "./api-utils"; | import { baseURL } from "./api-utils"; | ||||||
| import { apiReq } from "./api-utils"; | import { apiReq } from "./api-utils"; | ||||||
|  | import i18n from '@/i18n.js'; | ||||||
|  |  | ||||||
| const prefix = baseURL + "meal-plans/"; | const prefix = baseURL + "meal-plans/"; | ||||||
|  |  | ||||||
| @@ -15,9 +16,13 @@ const mealPlanURLs = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| export const mealplanAPI = { | export const mealplanAPI = { | ||||||
|   async create(postBody) { |   create(postBody) { | ||||||
|     let response = await apiReq.post(mealPlanURLs.create, postBody); |     return apiReq.post( | ||||||
|     return response; |       mealPlanURLs.create,  | ||||||
|  |       postBody, | ||||||
|  |       function() { return i18n.t('meal-plan.mealplan-creation-failed')}, | ||||||
|  |       function() { return i18n.t('meal-plan.mealplan-created'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async all() { |   async all() { | ||||||
| @@ -35,14 +40,21 @@ export const mealplanAPI = { | |||||||
|     return response; |     return response; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async delete(id) { |   delete(id) { | ||||||
|     let response = await apiReq.delete(mealPlanURLs.delete(id)); |     return apiReq.delete(mealPlanURLs.delete(id), | ||||||
|     return response; |       null, | ||||||
|  |       function() { return i18n.t('meal-plan.mealplan-deletion-failed'); }, | ||||||
|  |       function() { return i18n.t('meal-plan.mealplan-deleted'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async update(id, body) { |   update(id, body) { | ||||||
|     let response = await apiReq.put(mealPlanURLs.update(id), body); |     return apiReq.put( | ||||||
|     return response; |       mealPlanURLs.update(id),  | ||||||
|  |       body, | ||||||
|  |       function() { return i18n.t('meal-plan.mealplan-update-failed'); }, | ||||||
|  |       function() { return i18n.t('meal-plan.mealplan-updated'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async shoppingList(id) { |   async shoppingList(id) { | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import { baseURL } from "./api-utils"; | import { baseURL } from "./api-utils"; | ||||||
| import { apiReq } from "./api-utils"; | import { apiReq } from "./api-utils"; | ||||||
| import { store } from "../store"; | import { store } from "../store"; | ||||||
|  | import i18n from '@/i18n.js'; | ||||||
|  |  | ||||||
| const migrationBase = baseURL + "migrations"; | const migrationBase = baseURL + "migrations"; | ||||||
|  |  | ||||||
| @@ -17,8 +18,13 @@ export const migrationAPI = { | |||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|   async delete(folder, file) { |   async delete(folder, file) { | ||||||
|     let response = await apiReq.delete(migrationURLs.delete(folder, file)); |     const response = await apiReq.delete( | ||||||
|     return response.data; |       migrationURLs.delete(folder, file), | ||||||
|  |       null, | ||||||
|  |       function() { return i18n.t('general.file-folder-not-found'); }, | ||||||
|  |       function() { return i18n.t('migration.migration-data-removed'); } | ||||||
|  |     ); | ||||||
|  |     return response; | ||||||
|   }, |   }, | ||||||
|   async import(folder, file) { |   async import(folder, file) { | ||||||
|     let response = await apiReq.post(migrationURLs.import(folder, file)); |     let response = await apiReq.post(migrationURLs.import(folder, file)); | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { baseURL } from "./api-utils"; | import { baseURL } from "./api-utils"; | ||||||
| import { apiReq } from "./api-utils"; | import { apiReq } from "./api-utils"; | ||||||
| import { store } from "../store"; | import { store } from "../store"; | ||||||
| import { router } from "../main"; | import i18n from '@/i18n.js'; | ||||||
|  |  | ||||||
| const prefix = baseURL + "recipes/"; | const prefix = baseURL + "recipes/"; | ||||||
|  |  | ||||||
| @@ -26,9 +26,12 @@ export const recipeAPI = { | |||||||
|    * @returns {string} Recipe Slug |    * @returns {string} Recipe Slug | ||||||
|    */ |    */ | ||||||
|   async createByURL(recipeURL) { |   async createByURL(recipeURL) { | ||||||
|     let response = await apiReq.post(recipeURLs.createByURL, { |     const response = await apiReq.post( | ||||||
|       url: recipeURL, |       recipeURLs.createByURL,  | ||||||
|     }); |       { url: recipeURL }, | ||||||
|  |       function() { return i18n.t('recipe.recipe-creation-failed'); }, | ||||||
|  |       function() { return i18n.t('recipe.recipe-created'); } | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     store.dispatch("requestRecentRecipes"); |     store.dispatch("requestRecentRecipes"); | ||||||
|     return response; |     return response; | ||||||
| @@ -43,7 +46,12 @@ export const recipeAPI = { | |||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async create(recipeData) { |   async create(recipeData) { | ||||||
|     let response = await apiReq.post(recipeURLs.create, recipeData); |     const response = await apiReq.post( | ||||||
|  |       recipeURLs.create,  | ||||||
|  |       recipeData, | ||||||
|  |       function() { return i18n.t('recipe.recipe-creation-failed'); }, | ||||||
|  |       function() { return i18n.t('recipe.recipe-created'); } | ||||||
|  |     ); | ||||||
|     store.dispatch("requestRecentRecipes"); |     store.dispatch("requestRecentRecipes"); | ||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
| @@ -53,12 +61,22 @@ export const recipeAPI = { | |||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async updateImage(recipeSlug, fileObject) { |   updateImage(recipeSlug, fileObject, overrideSuccessMsg = false) { | ||||||
|     const fd = new FormData(); |     const formData = new FormData(); | ||||||
|     fd.append("image", fileObject); |     formData.append("image", fileObject); | ||||||
|     fd.append("extension", fileObject.name.split(".").pop()); |     formData.append("extension", fileObject.name.split(".").pop()); | ||||||
|     let response = apiReq.put(recipeURLs.updateImage(recipeSlug), fd); |  | ||||||
|     return response; |     let successMessage = null; | ||||||
|  |     if(!overrideSuccessMsg) { | ||||||
|  |       successMessage = function() { return overrideSuccessMsg ? null : i18n.t('recipe.recipe-image-updated'); }; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return apiReq.put( | ||||||
|  |       recipeURLs.updateImage(recipeSlug),  | ||||||
|  |       formData, | ||||||
|  |       function() { return i18n.t('general.image-upload-failed'); }, | ||||||
|  |       successMessage | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|    |    | ||||||
|   async createAsset(recipeSlug, fileObject, name, icon) { |   async createAsset(recipeSlug, fileObject, name, icon) { | ||||||
| @@ -71,16 +89,26 @@ export const recipeAPI = { | |||||||
|     return response; |     return response; | ||||||
|   }, |   }, | ||||||
|    |    | ||||||
|   async updateImagebyURL(slug, url) { |   updateImagebyURL(slug, url) { | ||||||
|     const response = apiReq.post(recipeURLs.updateImage(slug), { url: url }); |     return apiReq.post( | ||||||
|     return response; |       recipeURLs.updateImage(slug),  | ||||||
|  |       { url: url }, | ||||||
|  |       function() { return i18n.t('general.image-upload-failed'); }, | ||||||
|  |       function() { return i18n.t('recipe.recipe-image-updated'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async update(data) { |   async update(data) { | ||||||
|     console.log(data) |     let response = await apiReq.put( | ||||||
|     let response = await apiReq.put(recipeURLs.update(data.slug), data); |       recipeURLs.update(data.slug), | ||||||
|     store.dispatch("patchRecipe", response.data); |       data,  | ||||||
|     return response.data.slug; // ! Temporary until I rewrite to refresh page without additional request |       function() { return i18n.t('recipe.recipe-update-failed'); }, | ||||||
|  |       function() { return i18n.t('recipe.recipe-updated'); } | ||||||
|  |     ); | ||||||
|  |     if(response) { | ||||||
|  |       store.dispatch("patchRecipe", response.data); | ||||||
|  |       return response.data.slug; // ! Temporary until I rewrite to refresh page without additional request | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async patch(data) { |   async patch(data) { | ||||||
| @@ -89,10 +117,13 @@ export const recipeAPI = { | |||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async delete(recipeSlug) { |   delete(recipeSlug) { | ||||||
|     await apiReq.delete(recipeURLs.delete(recipeSlug)); |     return apiReq.delete( | ||||||
|     store.dispatch("requestRecentRecipes"); |       recipeURLs.delete(recipeSlug), | ||||||
|     router.push(`/`); |       null, | ||||||
|  |       function() { return i18n.t('recipe.unable-to-delete-recipe'); }, | ||||||
|  |       function() { return i18n.t('recipe.recipe-deleted'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async allSummary(start = 0, limit = 9999) { |   async allSummary(start = 0, limit = 9999) { | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import { baseURL } from "./api-utils"; | import { baseURL } from "./api-utils"; | ||||||
| import { apiReq } from "./api-utils"; | import { apiReq } from "./api-utils"; | ||||||
|  | import i18n from '@/i18n.js'; | ||||||
|  |  | ||||||
| const signUpPrefix = baseURL + "users/sign-ups"; | const signUpPrefix = baseURL + "users/sign-ups"; | ||||||
|  |  | ||||||
| @@ -16,15 +17,25 @@ export const signupAPI = { | |||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|   async createToken(data) { |   async createToken(data) { | ||||||
|     let response = await apiReq.post(signUpURLs.createToken, data); |     let response = await apiReq.post( | ||||||
|  |       signUpURLs.createToken,  | ||||||
|  |       data, | ||||||
|  |       function() { return i18n.t('signup.sign-up-link-creation-failed'); }, | ||||||
|  |       function() { return i18n.t('signup.sign-up-link-created'); } | ||||||
|  |     ); | ||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|   async deleteToken(token) { |   async deleteToken(token) { | ||||||
|     let response = await apiReq.delete(signUpURLs.deleteToken(token)); |     return await apiReq.delete(signUpURLs.deleteToken(token), | ||||||
|     return response.data; |     null, | ||||||
|  |     function() { return i18n.t('signup.sign-up-token-deletion-failed'); }, | ||||||
|  |     function() { return i18n.t('signup.sign-up-token-deleted'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|   async createUser(token, data) { |   async createUser(token, data) { | ||||||
|     let response = await apiReq.post(signUpURLs.createUser(token), data); |     return apiReq.post(signUpURLs.createUser(token), data, | ||||||
|     return response.data; |     function() { return i18n.t('user.you-are-not-allowed-to-create-a-user'); }, | ||||||
|  |     function() { return i18n.t('user.user-created'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import { baseURL } from "./api-utils"; | import { baseURL } from "./api-utils"; | ||||||
| import { apiReq } from "./api-utils"; | import { apiReq } from "./api-utils"; | ||||||
| import { store } from "@/store"; | import { store } from "@/store"; | ||||||
|  | import i18n from '@/i18n.js'; | ||||||
|  |  | ||||||
| const settingsBase = baseURL + "site-settings"; | const settingsBase = baseURL + "site-settings"; | ||||||
|  |  | ||||||
| @@ -19,9 +20,16 @@ export const siteSettingsAPI = { | |||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async update(body) { |   async update(body) { | ||||||
|     let response = await apiReq.put(settingsURLs.updateSiteSettings, body); |     const response = await apiReq.put( | ||||||
|     store.dispatch("requestSiteSettings"); |       settingsURLs.updateSiteSettings,  | ||||||
|     return response.data; |       body, | ||||||
|  |       function() { return i18n.t('settings.settings-update-failed'); }, | ||||||
|  |       function() { return i18n.t('settings.settings-updated'); } | ||||||
|  |     ); | ||||||
|  |     if(response) { | ||||||
|  |       store.dispatch("requestSiteSettings"); | ||||||
|  |     } | ||||||
|  |     return response; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async getPages() { |   async getPages() { | ||||||
| @@ -34,23 +42,39 @@ export const siteSettingsAPI = { | |||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async createPage(body) { |   createPage(body) { | ||||||
|     let response = await apiReq.post(settingsURLs.customPages, body); |     return apiReq.post( | ||||||
|     return response.data; |       settingsURLs.customPages,  | ||||||
|  |       body, | ||||||
|  |       function() { return i18n.t('page.page-creation-failed'); }, | ||||||
|  |       function() { return i18n.t('page.new-page-created'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async deletePage(id) { |   async deletePage(id) { | ||||||
|     let response = await apiReq.delete(settingsURLs.customPage(id)); |     return await apiReq.delete( | ||||||
|     return response.data; |       settingsURLs.customPage(id), | ||||||
|  |       null, | ||||||
|  |       function() { return i18n.t('page.page-deletion-failed'); }, | ||||||
|  |       function() { return i18n.t('page.page-deleted'); }); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async updatePage(body) { |   updatePage(body) { | ||||||
|     let response = await apiReq.put(settingsURLs.customPage(body.id), body); |     return apiReq.put( | ||||||
|     return response.data; |       settingsURLs.customPage(body.id), | ||||||
|  |       body, | ||||||
|  |       function() { return i18n.t('page.page-update-failed'); }, | ||||||
|  |       function() { return i18n.t('page.page-updated'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async updateAllPages(allPages) { |   async updateAllPages(allPages) { | ||||||
|     let response = await apiReq.put(settingsURLs.customPages, allPages); |     let response = await apiReq.put( | ||||||
|  |       settingsURLs.customPages,  | ||||||
|  |       allPages, | ||||||
|  |       function() { return i18n.t('page.pages-update-failed'); }, | ||||||
|  |       function() { return i18n.t('page.pages-updated'); } | ||||||
|  |     ); | ||||||
|     return response; |     return response; | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import { baseURL } from "./api-utils"; | import { baseURL } from "./api-utils"; | ||||||
| import { apiReq } from "./api-utils"; | import { apiReq } from "./api-utils"; | ||||||
|  | import i18n from '@/i18n.js'; | ||||||
|  |  | ||||||
| const prefix = baseURL + "themes"; | const prefix = baseURL + "themes"; | ||||||
|  |  | ||||||
| @@ -23,21 +24,31 @@ export const themeAPI = { | |||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async create(postBody) { |   async create(postBody) { | ||||||
|     let response = await apiReq.post(settingsURLs.createTheme, postBody); |     return await apiReq.post( | ||||||
|     return response.data; |       settingsURLs.createTheme,  | ||||||
|  |       postBody, | ||||||
|  |       function() { return i18n.t('settings.theme.error-creating-theme-see-log-file'); }, | ||||||
|  |       function() { return i18n.t('settings.theme.theme-saved'); }); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async update(themeName, colors) { |   update(themeName, colors) { | ||||||
|     const body = { |     const body = { | ||||||
|       name: themeName, |       name: themeName, | ||||||
|       colors: colors, |       colors: colors, | ||||||
|     }; |     }; | ||||||
|     let response = await apiReq.put(settingsURLs.updateTheme(themeName), body); |     return apiReq.put( | ||||||
|     return response.data; |       settingsURLs.updateTheme(themeName),  | ||||||
|  |       body, | ||||||
|  |       function() { return i18n.t('settings.theme.error-updating-theme'); }, | ||||||
|  |       function() { return i18n.t('settings.theme.theme-updated'); }); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async delete(themeName) { |   delete(themeName) { | ||||||
|     let response = await apiReq.delete(settingsURLs.deleteTheme(themeName)); |     return apiReq.delete( | ||||||
|     return response.data; |       settingsURLs.deleteTheme(themeName), | ||||||
|  |       null, | ||||||
|  |       function() { return i18n.t('settings.theme.error-deleting-theme'); }, | ||||||
|  |       function() { return i18n.t('settings.theme.theme-deleted'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,15 +1,16 @@ | |||||||
| import { apiReq } from "./api-utils"; | import { apiReq } from "./api-utils"; | ||||||
|  | import i18n from '@/i18n.js'; | ||||||
|  |  | ||||||
| export const utilsAPI = { | export const utilsAPI = { | ||||||
|   // import { api } from "@/api"; |   // import { api } from "@/api"; | ||||||
|   async uploadFile(url, fileObject) { |   uploadFile(url, fileObject) { | ||||||
|     console.log("API Called"); |     console.log("API Called"); | ||||||
|  |  | ||||||
|     let response = await apiReq.post(url, fileObject, { |     return apiReq.post( | ||||||
|       headers: { |       url, | ||||||
|         "Content-Type": "multipart/form-data", |       fileObject, | ||||||
|       }, |       function() { return i18n.t('general.failure-uploading-file'); }, | ||||||
|     }); |       function() { return i18n.t('general.file-uploaded'); } | ||||||
|     return response.data; |     ); | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import { baseURL } from "./api-utils"; | import { baseURL } from "./api-utils"; | ||||||
| import { apiReq } from "./api-utils"; | import { apiReq } from "./api-utils"; | ||||||
| import axios from "axios"; | import axios from "axios"; | ||||||
|  | import i18n from '@/i18n.js'; | ||||||
| const authPrefix = baseURL + "auth"; | const authPrefix = baseURL + "auth"; | ||||||
| const userPrefix = baseURL + "users"; | const userPrefix = baseURL + "users"; | ||||||
|  |  | ||||||
| @@ -17,13 +18,23 @@ const usersURLs = { | |||||||
|   resetPassword: id => `${userPrefix}/${id}/reset-password`, |   resetPassword: id => `${userPrefix}/${id}/reset-password`, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | function deleteErrorText(response) { | ||||||
|  |   switch(response.data.detail) { | ||||||
|  |     case 'SUPER_USER': | ||||||
|  |       return i18n.t('user.error-cannot-delete-super-user'); | ||||||
|  |  | ||||||
|  |     default: | ||||||
|  |       return i18n.t('user.you-are-not-allowed-to-delete-this-user'); | ||||||
|  |   } | ||||||
|  | } | ||||||
| export const userAPI = { | export const userAPI = { | ||||||
|   async login(formData) { |   async login(formData) { | ||||||
|     let response = await apiReq.post(authURLs.token, formData, { |     let response = await apiReq.post( | ||||||
|       headers: { |       authURLs.token,  | ||||||
|         "Content-Type": "application/x-www-form-urlencoded", |       formData, | ||||||
|       }, |       function() { return i18n.t('user.incorrect-username-or-password'); }, | ||||||
|     }); |       function() { return i18n.t('user.user-successfully-logged-in'); } | ||||||
|  |     ); | ||||||
|     return response; |     return response; | ||||||
|   }, |   }, | ||||||
|   async refresh() { |   async refresh() { | ||||||
| @@ -36,9 +47,13 @@ export const userAPI = { | |||||||
|     let response = await apiReq.get(usersURLs.users); |     let response = await apiReq.get(usersURLs.users); | ||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|   async create(user) { |   create(user) { | ||||||
|     let response = await apiReq.post(usersURLs.users, user); |     return apiReq.post( | ||||||
|     return response.data; |       usersURLs.users,  | ||||||
|  |       user, | ||||||
|  |       function() { return i18n.t('user.user-creation-failed'); }, | ||||||
|  |       function() { return i18n.t('user.user-created'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|   async self() { |   async self() { | ||||||
|     let response = await apiReq.get(usersURLs.self); |     let response = await apiReq.get(usersURLs.self); | ||||||
| @@ -48,20 +63,37 @@ export const userAPI = { | |||||||
|     let response = await apiReq.get(usersURLs.userID(id)); |     let response = await apiReq.get(usersURLs.userID(id)); | ||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|   async update(user) { |   update(user) { | ||||||
|     let response = await apiReq.put(usersURLs.userID(user.id), user); |     return apiReq.put( | ||||||
|     return response.data; |       usersURLs.userID(user.id),  | ||||||
|  |       user, | ||||||
|  |       function() { return i18n.t('user.user-update-failed'); }, | ||||||
|  |       function() { return i18n.t('user.user-updated'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|   async changePassword(id, password) { |   changePassword(id, password) { | ||||||
|     let response = await apiReq.put(usersURLs.password(id), password); |     return apiReq.put( | ||||||
|     return response.data; |       usersURLs.password(id),  | ||||||
|  |       password, | ||||||
|  |       function() { return i18n.t('user.existing-password-does-not-match'); }, | ||||||
|  |       function() { return i18n.t('user.password-updated'); } | ||||||
|  |       ); | ||||||
|   }, |   }, | ||||||
|   async delete(id) { |    | ||||||
|     let response = await apiReq.delete(usersURLs.userID(id)); |   delete(id) { | ||||||
|     return response.data; |     return apiReq.delete( | ||||||
|  |       usersURLs.userID(id), | ||||||
|  |       null, | ||||||
|  |       deleteErrorText, | ||||||
|  |       function() { return i18n.t('user.user-deleted'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
|   async resetPassword(id) { |   resetPassword(id) { | ||||||
|     let response = await apiReq.put(usersURLs.resetPassword(id)); |     return apiReq.put( | ||||||
|     return response.data; |       usersURLs.resetPassword(id), | ||||||
|  |       null, | ||||||
|  |       function() { return i18n.t('user.password-reset-failed'); }, | ||||||
|  |       function() { return i18n.t('user.password-has-been-reset-to-the-default-password'); } | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -90,7 +90,7 @@ export default { | |||||||
|   computed: { |   computed: { | ||||||
|     inputLabel() { |     inputLabel() { | ||||||
|       if (!this.showLabel) return null; |       if (!this.showLabel) return null; | ||||||
|       return this.tagSelector ? this.$t('recipe.tags') : this.$t('recipe.categories'); |       return this.tagSelector ? this.$t('tag.tags') : this.$t('recipe.categories'); | ||||||
|     }, |     }, | ||||||
|     activeItems() { |     activeItems() { | ||||||
|       let ItemObjects = []; |       let ItemObjects = []; | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ | |||||||
|             mdi-import |             mdi-import | ||||||
|           </v-icon> |           </v-icon> | ||||||
|           <v-toolbar-title class="headline"> |           <v-toolbar-title class="headline"> | ||||||
|             Import Summary |             {{ $t("settings.backup.import-summary") }} | ||||||
|           </v-toolbar-title> |           </v-toolbar-title> | ||||||
|           <v-spacer></v-spacer> |           <v-spacer></v-spacer> | ||||||
|         </v-app-bar> |         </v-app-bar> | ||||||
| @@ -18,8 +18,8 @@ | |||||||
|                 <div> |                 <div> | ||||||
|                   <h3>{{ values.title }}</h3> |                   <h3>{{ values.title }}</h3> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div class="success--text">Success: {{ values.success }}</div> |                 <div class="success--text">{{ $t("general.success-count", { count: values.success }) }}</div> | ||||||
|                 <div class="error--text">Failed: {{ values.failure }}</div> |                 <div class="error--text">{{ $t("general.failed-count", { count: values.failure }) }}</div> | ||||||
|               </v-card-text> |               </v-card-text> | ||||||
|             </div> |             </div> | ||||||
|           </v-row> |           </v-row> | ||||||
| @@ -28,7 +28,7 @@ | |||||||
|           <v-tab>{{ $t("general.recipes") }}</v-tab> |           <v-tab>{{ $t("general.recipes") }}</v-tab> | ||||||
|           <v-tab>{{ $t("general.themes") }}</v-tab> |           <v-tab>{{ $t("general.themes") }}</v-tab> | ||||||
|           <v-tab>{{ $t("general.settings") }}</v-tab> |           <v-tab>{{ $t("general.settings") }}</v-tab> | ||||||
|           <v-tab> Pages </v-tab> |           <v-tab> {{ $t("settings.pages") }} </v-tab> | ||||||
|           <v-tab>{{ $t("general.users") }}</v-tab> |           <v-tab>{{ $t("general.users") }}</v-tab> | ||||||
|           <v-tab>{{ $t("general.groups") }}</v-tab> |           <v-tab>{{ $t("general.groups") }}</v-tab> | ||||||
|         </v-tabs> |         </v-tabs> | ||||||
| @@ -59,24 +59,30 @@ export default { | |||||||
|     userData: [], |     userData: [], | ||||||
|     groupData: [], |     groupData: [], | ||||||
|     pageData: [], |     pageData: [], | ||||||
|     importHeaders: [ |  | ||||||
|       { |  | ||||||
|         text: "Status", |  | ||||||
|         value: "status", |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         text: "Name", |  | ||||||
|         align: "start", |  | ||||||
|         sortable: true, |  | ||||||
|         value: "name", |  | ||||||
|       }, |  | ||||||
|  |  | ||||||
|       { text: "Exception", value: "data-table-expand", align: "center" }, |  | ||||||
|     ], |  | ||||||
|     allDataTables: [], |     allDataTables: [], | ||||||
|   }), |   }), | ||||||
|  |  | ||||||
|   computed: { |   computed: { | ||||||
|  |      | ||||||
|  |     importHeaders() { | ||||||
|  |       return [ | ||||||
|  |         { | ||||||
|  |           text: this.$t('general.status'), | ||||||
|  |           value: "status", | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           text: this.$t('general.name'), | ||||||
|  |           align: "start", | ||||||
|  |           sortable: true, | ||||||
|  |           value: "name", | ||||||
|  |         }, | ||||||
|  |         {  | ||||||
|  |           text: this.$t('general.exception'),  | ||||||
|  |           value: "data-table-expand",  | ||||||
|  |           align: "center"  | ||||||
|  |         }, | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|     recipeNumbers() { |     recipeNumbers() { | ||||||
|       return this.calculateNumbers(this.$t("general.recipes"), this.recipeData); |       return this.calculateNumbers(this.$t("general.recipes"), this.recipeData); | ||||||
|     }, |     }, | ||||||
| @@ -96,7 +102,7 @@ export default { | |||||||
|       return this.calculateNumbers(this.$t("general.groups"), this.groupData); |       return this.calculateNumbers(this.$t("general.groups"), this.groupData); | ||||||
|     }, |     }, | ||||||
|     pageNumbers() { |     pageNumbers() { | ||||||
|       return this.calculateNumbers("Pages", this.pageData); |       return this.calculateNumbers(this.$t("settings.pages"), this.pageData); | ||||||
|     }, |     }, | ||||||
|     allNumbers() { |     allNumbers() { | ||||||
|       return [ |       return [ | ||||||
|   | |||||||
| @@ -91,18 +91,12 @@ export default { | |||||||
|       let formData = new FormData(); |       let formData = new FormData(); | ||||||
|       formData.append("username", this.user.email); |       formData.append("username", this.user.email); | ||||||
|       formData.append("password", this.user.password); |       formData.append("password", this.user.password); | ||||||
|       let key; |       const response = await api.users.login(formData); | ||||||
|       try { |       if (!response) { | ||||||
|         key = await api.users.login(formData); |  | ||||||
|       } catch { |  | ||||||
|         this.error = true; |         this.error = true; | ||||||
|       } |  | ||||||
|       if (key.status != 200) { |  | ||||||
|         this.error = true; |  | ||||||
|         this.loading = false; |  | ||||||
|       } else { |       } else { | ||||||
|         this.clear(); |         this.clear(); | ||||||
|         this.$store.commit("setToken", key.data.access_token); |         this.$store.commit("setToken", response.data.access_token); | ||||||
|         this.$emit("logged-in"); |         this.$emit("logged-in"); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,13 +13,13 @@ | |||||||
|         class="mr-2" |         class="mr-2" | ||||||
|       > |       > | ||||||
|       </v-progress-circular> |       </v-progress-circular> | ||||||
|       <v-toolbar-title class="headline"> Sign Up </v-toolbar-title> |       <v-toolbar-title class="headline">  | ||||||
|  |         {{$t('signup.sign-up')}}  | ||||||
|  |       </v-toolbar-title> | ||||||
|       <v-spacer></v-spacer> |       <v-spacer></v-spacer> | ||||||
|     </v-app-bar> |     </v-app-bar> | ||||||
|     <v-card-text> |     <v-card-text> | ||||||
|       Welcome to Mealie! To become a user of this instance you are required to |       {{$t('signup.welcome-to-mealie')}} | ||||||
|       have a valid invitation link. If you haven't recieved an invitation you |  | ||||||
|       are unable to sign-up. To recieve a link, contact the sites administrator. |  | ||||||
|       <v-divider class="mt-3"></v-divider> |       <v-divider class="mt-3"></v-divider> | ||||||
|       <v-form ref="signUpForm" @submit.prevent="signUp"> |       <v-form ref="signUpForm" @submit.prevent="signUp"> | ||||||
|         <v-text-field |         <v-text-field | ||||||
| @@ -28,7 +28,7 @@ | |||||||
|           prepend-icon="mdi-account" |           prepend-icon="mdi-account" | ||||||
|           validate-on-blur |           validate-on-blur | ||||||
|           :rules="[existsRule]" |           :rules="[existsRule]" | ||||||
|           label="Display Name" |           :label="$t('signup.display-name')" | ||||||
|           type="email" |           type="email" | ||||||
|         ></v-text-field> |         ></v-text-field> | ||||||
|         <v-text-field |         <v-text-field | ||||||
| @@ -59,7 +59,7 @@ | |||||||
|           :type="showPassword ? 'text' : 'password'" |           :type="showPassword ? 'text' : 'password'" | ||||||
|           :append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'" |           :append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'" | ||||||
|           :rules="[ |           :rules="[ | ||||||
|             user.password === user.passwordConfirm || 'Password must match', |             user.password === user.passwordConfirm || $t('user.password-must-match'), | ||||||
|           ]" |           ]" | ||||||
|           @click:append="showPassword = !showPassword" |           @click:append="showPassword = !showPassword" | ||||||
|         ></v-text-field> |         ></v-text-field> | ||||||
| @@ -71,11 +71,11 @@ | |||||||
|             block="block" |             block="block" | ||||||
|             type="submit" |             type="submit" | ||||||
|           > |           > | ||||||
|             Sign Up |             {{$t('signup.sign-up')}} | ||||||
|           </v-btn> |           </v-btn> | ||||||
|         </v-card-actions> |         </v-card-actions> | ||||||
|         <v-alert dense v-if="error" outlined class="mt-3 mb-0" type="error"> |         <v-alert dense v-if="error" outlined class="mt-3 mb-0" type="error"> | ||||||
|           Error Signing Up |           {{$t('signup.error-signing-up')}} | ||||||
|         </v-alert> |         </v-alert> | ||||||
|       </v-form> |       </v-form> | ||||||
|     </v-card-text> |     </v-card-text> | ||||||
| @@ -132,18 +132,16 @@ export default { | |||||||
|         admin: false, |         admin: false, | ||||||
|       }; |       }; | ||||||
|  |  | ||||||
|       let successUser = false; |  | ||||||
|       if (this.$refs.signUpForm.validate()) { |       if (this.$refs.signUpForm.validate()) { | ||||||
|         let response = await api.signUps.createUser(this.token, userData); |         if (await api.signUps.createUser(this.token, userData)) { | ||||||
|         successUser = response.snackbar.text.includes("Created"); |           this.$emit("user-created"); | ||||||
|  |           this.$router.push("/"); | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|        |        | ||||||
|       this.$emit("user-created"); |  | ||||||
|  |  | ||||||
|       this.loading = false; |       this.loading = false; | ||||||
|       if (successUser) { |  | ||||||
|         this.$router.push("/"); |  | ||||||
|       } |  | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -36,8 +36,9 @@ export default { | |||||||
|       return utils.getDateAsPythonDate(dateObject); |       return utils.getDateAsPythonDate(dateObject); | ||||||
|     }, |     }, | ||||||
|     async update() { |     async update() { | ||||||
|       await api.mealPlans.update(this.mealPlan.uid, this.mealPlan); |       if (await api.mealPlans.update(this.mealPlan.uid, this.mealPlan)) { | ||||||
|       this.$emit("updated"); |         this.$emit("updated"); | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -197,11 +197,12 @@ export default { | |||||||
|         endDate: this.endDate, |         endDate: this.endDate, | ||||||
|         meals: this.meals, |         meals: this.meals, | ||||||
|       }; |       }; | ||||||
|       await api.mealPlans.create(mealBody); |       if (await api.mealPlans.create(mealBody)) { | ||||||
|       this.$emit(CREATE_EVENT); |         this.$emit(CREATE_EVENT); | ||||||
|       this.meals = []; |         this.meals = []; | ||||||
|       this.startDate = null; |         this.startDate = null; | ||||||
|       this.endDate = null; |         this.endDate = null; | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     getImage(image) { |     getImage(image) { | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ | |||||||
|           <v-icon left> |           <v-icon left> | ||||||
|             mdi-image |             mdi-image | ||||||
|           </v-icon> |           </v-icon> | ||||||
|           {{ $t("recipe.image") }} |           {{ $t("general.image") }} | ||||||
|         </v-btn> |         </v-btn> | ||||||
|       </template> |       </template> | ||||||
|       <v-card width="400"> |       <v-card width="400"> | ||||||
| @@ -71,8 +71,9 @@ export default { | |||||||
|     }, |     }, | ||||||
|     async getImageFromURL() { |     async getImageFromURL() { | ||||||
|       this.loading = true; |       this.loading = true; | ||||||
|       const response = await api.recipes.updateImagebyURL(this.slug, this.url); |       if (await api.recipes.updateImagebyURL(this.slug, this.url)) { | ||||||
|       if (response) this.$emit(REFRESH_EVENT); |         this.$emit(REFRESH_EVENT); | ||||||
|  |       } | ||||||
|       this.loading = false; |       this.loading = false; | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   | |||||||
| @@ -74,7 +74,7 @@ | |||||||
|             :show-label="false" |             :show-label="false" | ||||||
|           /> |           /> | ||||||
|  |  | ||||||
|           <h2 class="mt-4">{{ $t("recipe.tags") }}</h2> |           <h2 class="mt-4">{{ $t("tag.tags") }}</h2> | ||||||
|           <CategoryTagSelector |           <CategoryTagSelector | ||||||
|             :return-object="false" |             :return-object="false" | ||||||
|             v-model="value.tags" |             v-model="value.tags" | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ | |||||||
|             </v-card> |             </v-card> | ||||||
|             <v-card class="mt-2" v-if="tags.length > 0"> |             <v-card class="mt-2" v-if="tags.length > 0"> | ||||||
|               <v-card-title class="py-2"> |               <v-card-title class="py-2"> | ||||||
|                 {{ $t("recipe.tags") }} |                 {{ $t("tag.tags") }} | ||||||
|               </v-card-title> |               </v-card-title> | ||||||
|               <v-divider class="mx-2"></v-divider> |               <v-divider class="mx-2"></v-divider> | ||||||
|               <v-card-text> |               <v-card-text> | ||||||
| @@ -69,7 +69,7 @@ | |||||||
|       </v-row> |       </v-row> | ||||||
|       <div v-if="!medium"> |       <div v-if="!medium"> | ||||||
|         <RecipeChips :title="$t('recipe.categories')" :items="categories" /> |         <RecipeChips :title="$t('recipe.categories')" :items="categories" /> | ||||||
|         <RecipeChips :title="$t('recipe.tags')" :items="tags" /> |         <RecipeChips :title="$t('tag.tags')" :items="tags" /> | ||||||
|         <Nutrition :value="nutrition" :edit="false" /> |         <Nutrition :value="nutrition" :edit="false" /> | ||||||
|         <Assets :value="assets" :edit="false" :slug="slug" /> |         <Assets :value="assets" :edit="false" :slug="slug" /> | ||||||
|       </div> |       </div> | ||||||
|   | |||||||
| @@ -55,10 +55,10 @@ export default { | |||||||
|         let formData = new FormData(); |         let formData = new FormData(); | ||||||
|         formData.append(this.fileName, this.file); |         formData.append(this.fileName, this.file); | ||||||
|  |  | ||||||
|         await api.utils.uploadFile(this.url, formData); |         if(await api.utils.uploadFile(this.url, formData)) { | ||||||
|  |           this.$emit(UPLOAD_EVENT); | ||||||
|  |         } | ||||||
|         this.isSelecting = false; |         this.isSelecting = false; | ||||||
|         this.$emit(UPLOAD_EVENT); |  | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     onButtonClick() { |     onButtonClick() { | ||||||
|   | |||||||
| @@ -105,17 +105,15 @@ export default { | |||||||
|     async createRecipe() { |     async createRecipe() { | ||||||
|       if (this.$refs.urlForm.validate()) { |       if (this.$refs.urlForm.validate()) { | ||||||
|         this.processing = true; |         this.processing = true; | ||||||
|         let response = await api.recipes.createByURL(this.recipeURL); |         const response = await api.recipes.createByURL(this.recipeURL); | ||||||
|         if (response.status !== 201) { |  | ||||||
|           this.error = true; |  | ||||||
|           this.processing = false; |  | ||||||
|           return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         this.addRecipe = false; |  | ||||||
|         this.processing = false; |         this.processing = false; | ||||||
|         this.recipeURL = ""; |         if (response) { | ||||||
|         this.$router.push(`/recipe/${response.data}`); |           this.addRecipe = false; | ||||||
|  |           this.recipeURL = ""; | ||||||
|  |           this.$router.push(`/recipe/${response.data}`); | ||||||
|  |         } else { | ||||||
|  |           this.error = true; | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -44,9 +44,9 @@ export default { | |||||||
|   components: { |   components: { | ||||||
|     LoginDialog, |     LoginDialog, | ||||||
|   }, |   }, | ||||||
|   data: function() { |   computed: { | ||||||
|     return { |     items() { | ||||||
|       items: [ |       return [ | ||||||
|         { |         { | ||||||
|           icon: "mdi-account", |           icon: "mdi-account", | ||||||
|           title: "Login", |           title: "Login", | ||||||
| @@ -83,10 +83,8 @@ export default { | |||||||
|           nav: "/admin", |           nav: "/admin", | ||||||
|           restricted: true, |           restricted: true, | ||||||
|         }, |         }, | ||||||
|       ], |       ] | ||||||
|     }; |     }, | ||||||
|   }, |  | ||||||
|   computed: { |  | ||||||
|     filteredItems() { |     filteredItems() { | ||||||
|       if (this.loggedIn) { |       if (this.loggedIn) { | ||||||
|         return this.items.filter(x => x.restricted == true); |         return this.items.filter(x => x.restricted == true); | ||||||
|   | |||||||
| @@ -13,11 +13,22 @@ | |||||||
|     "demo": "Demo", |     "demo": "Demo", | ||||||
|     "demo-status": "Demo Status", |     "demo-status": "Demo Status", | ||||||
|     "development": "Development", |     "development": "Development", | ||||||
|  |     "download-log": "Download Log", | ||||||
|  |     "download-recipe-json": "Download Recipe JSON", | ||||||
|     "not-demo": "Not Demo", |     "not-demo": "Not Demo", | ||||||
|     "production": "Production", |     "production": "Production", | ||||||
|     "sqlite-file": "SQLite File", |     "sqlite-file": "SQLite File", | ||||||
|     "version": "Version" |     "version": "Version" | ||||||
|   }, |   }, | ||||||
|  |   "category": { | ||||||
|  |     "category-created": "Category created", | ||||||
|  |     "category-creation-failed": "Category creation failed", | ||||||
|  |     "category-deleted": "Category Deleted", | ||||||
|  |     "category-deletion-failed": "Category deletion failed", | ||||||
|  |     "category-filter": "Category Filter", | ||||||
|  |     "category-update-failed": "Category update failed", | ||||||
|  |     "category-updated": "Category updated" | ||||||
|  |   }, | ||||||
|   "general": { |   "general": { | ||||||
|     "apply": "Apply", |     "apply": "Apply", | ||||||
|     "cancel": "Cancel", |     "cancel": "Cancel", | ||||||
| @@ -30,29 +41,40 @@ | |||||||
|     "download": "Download", |     "download": "Download", | ||||||
|     "edit": "Edit", |     "edit": "Edit", | ||||||
|     "enabled": "Enabled", |     "enabled": "Enabled", | ||||||
|  |     "exception": "Exception", | ||||||
|  |     "failed-count": "Failed: {count}", | ||||||
|  |     "failure-uploading-file": "Failure uploading file", | ||||||
|     "field-required": "Field Required", |     "field-required": "Field Required", | ||||||
|  |     "file-folder-not-found": "File/folder not found", | ||||||
|  |     "file-uploaded": "File uploaded", | ||||||
|     "filter": "Filter", |     "filter": "Filter", | ||||||
|     "friday": "Friday", |     "friday": "Friday", | ||||||
|     "get": "Get", |     "get": "Get", | ||||||
|     "groups": "Groups", |     "groups": "Groups", | ||||||
|  |     "image": "Image", | ||||||
|  |     "image-upload-failed": "Image upload failed", | ||||||
|     "import": "Import", |     "import": "Import", | ||||||
|  |     "keyword": "Keyword", | ||||||
|     "monday": "Monday", |     "monday": "Monday", | ||||||
|     "name": "Name", |     "name": "Name", | ||||||
|     "no": "No", |     "no": "No", | ||||||
|     "ok": "OK", |     "ok": "OK", | ||||||
|     "options": "Options", |     "options": "Options:", | ||||||
|     "random": "Random", |     "random": "Random", | ||||||
|     "recent": "Recent", |     "recent": "Recent", | ||||||
|     "recipes": "Recipes", |     "recipes": "Recipes", | ||||||
|  |     "rename-object": "Rename {0}", | ||||||
|     "reset": "Reset", |     "reset": "Reset", | ||||||
|     "saturday": "Saturday", |     "saturday": "Saturday", | ||||||
|     "save": "Save", |     "save": "Save", | ||||||
|     "settings": "Settings", |     "settings": "Settings", | ||||||
|     "sort": "Sort", |     "sort": "Sort", | ||||||
|     "sort-alphabetically": "A-Z", |     "sort-alphabetically": "A-Z", | ||||||
|  |     "status": "Status", | ||||||
|     "submit": "Submit", |     "submit": "Submit", | ||||||
|  |     "success-count": "Success: {count}", | ||||||
|     "sunday": "Sunday", |     "sunday": "Sunday", | ||||||
|     "templates": "Templates", |     "templates": "Templates:", | ||||||
|     "themes": "Themes", |     "themes": "Themes", | ||||||
|     "thursday": "Thursday", |     "thursday": "Thursday", | ||||||
|     "token": "Token", |     "token": "Token", | ||||||
| @@ -64,6 +86,25 @@ | |||||||
|     "wednesday": "Wednesday", |     "wednesday": "Wednesday", | ||||||
|     "yes": "Yes" |     "yes": "Yes" | ||||||
|   }, |   }, | ||||||
|  |   "group": { | ||||||
|  |     "are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?", | ||||||
|  |     "cannot-delete-default-group": "Cannot delete default group", | ||||||
|  |     "cannot-delete-group-with-users": "Cannot delete group with users", | ||||||
|  |     "confirm-group-deletion": "Confirm Group Deletion", | ||||||
|  |     "create-group": "Create Group", | ||||||
|  |     "error-updating-group": "Error updating group", | ||||||
|  |     "group": "Group", | ||||||
|  |     "group-deleted": "Group deleted", | ||||||
|  |     "group-deletion-failed": "Group deletion failed", | ||||||
|  |     "group-id-with-value": "Group ID: {groupID}", | ||||||
|  |     "group-name": "Group Name", | ||||||
|  |     "group-not-found": "Group not found", | ||||||
|  |     "groups": "Groups", | ||||||
|  |     "groups-can-only-be-set-by-administrators": "Groups can only be set by administrators", | ||||||
|  |     "user-group": "User Group", | ||||||
|  |     "user-group-created": "User Group Created", | ||||||
|  |     "user-group-creation-failed": "User Group Creation Failed" | ||||||
|  |   }, | ||||||
|   "meal-plan": { |   "meal-plan": { | ||||||
|     "create-a-new-meal-plan": "Create a New Meal Plan", |     "create-a-new-meal-plan": "Create a New Meal Plan", | ||||||
|     "dinner-this-week": "Dinner This Week", |     "dinner-this-week": "Dinner This Week", | ||||||
| @@ -73,6 +114,14 @@ | |||||||
|     "group": "Group (Beta)", |     "group": "Group (Beta)", | ||||||
|     "meal-planner": "Meal Planner", |     "meal-planner": "Meal Planner", | ||||||
|     "meal-plans": "Meal Plans", |     "meal-plans": "Meal Plans", | ||||||
|  |     "mealplan-created": "Mealplan created", | ||||||
|  |     "mealplan-creation-failed": "Mealplan creation failed", | ||||||
|  |     "mealplan-deleted": "Mealplan Deleted", | ||||||
|  |     "mealplan-deletion-failed": "Mealplan deletion failed", | ||||||
|  |     "mealplan-update-failed": "Mealplan update failed", | ||||||
|  |     "mealplan-updated": "Mealplan Updated", | ||||||
|  |     "no-meal-plan-defined-yet": "No meal plan defined yet", | ||||||
|  |     "no-meal-planned-for-today": "No meal planned for today", | ||||||
|     "only-recipes-with-these-categories-will-be-used-in-meal-plans": "Only recipes with these categories will be used in Meal Plans", |     "only-recipes-with-these-categories-will-be-used-in-meal-plans": "Only recipes with these categories will be used in Meal Plans", | ||||||
|     "planner": "Planner", |     "planner": "Planner", | ||||||
|     "quick-week": "Quick Week", |     "quick-week": "Quick Week", | ||||||
| @@ -84,6 +133,7 @@ | |||||||
|       "description": "Migrate data from Chowdown", |       "description": "Migrate data from Chowdown", | ||||||
|       "title": "Chowdown" |       "title": "Chowdown" | ||||||
|     }, |     }, | ||||||
|  |     "migration-data-removed": "Migration data removed", | ||||||
|     "nextcloud": { |     "nextcloud": { | ||||||
|       "description": "Migrate data from a Nextcloud Cookbook intance", |       "description": "Migrate data from a Nextcloud Cookbook intance", | ||||||
|       "title": "Nextcloud Cookbook" |       "title": "Nextcloud Cookbook" | ||||||
| @@ -102,23 +152,30 @@ | |||||||
|   "page": { |   "page": { | ||||||
|     "all-recipes": "All Recipes", |     "all-recipes": "All Recipes", | ||||||
|     "home-page": "Home Page", |     "home-page": "Home Page", | ||||||
|  |     "new-page-created": "New page created", | ||||||
|  |     "page-creation-failed": "Page creation failed", | ||||||
|  |     "page-deleted": "Page deleted", | ||||||
|  |     "page-deletion-failed": "Page deletion failed", | ||||||
|  |     "page-update-failed": "Page update failed", | ||||||
|  |     "page-updated": "Page updated", | ||||||
|  |     "pages-update-failed": "Pages update failed", | ||||||
|  |     "pages-updated": "Pages updated", | ||||||
|     "recent": "Recent" |     "recent": "Recent" | ||||||
|   }, |   }, | ||||||
|   "recipe": { |   "recipe": { | ||||||
|     "assets": "Assets", |  | ||||||
|     "add-key": "Add Key", |     "add-key": "Add Key", | ||||||
|     "api-extras": "API Extras", |     "api-extras": "API Extras", | ||||||
|  |     "assets": "Assets", | ||||||
|     "calories": "Calories", |     "calories": "Calories", | ||||||
|     "calories-suffix": "calories", |     "calories-suffix": "calories", | ||||||
|  |     "carbohydrate-content": "Carbohydrate", | ||||||
|     "categories": "Categories", |     "categories": "Categories", | ||||||
|     "delete-confirmation": "Are you sure you want to delete this recipe?", |     "delete-confirmation": "Are you sure you want to delete this recipe?", | ||||||
|     "delete-recipe": "Delete Recipe", |     "delete-recipe": "Delete Recipe", | ||||||
|     "description": "Description", |     "description": "Description", | ||||||
|     "fat-content": "Fat", |     "fat-content": "Fat", | ||||||
|     "fiber-content": "Fiber", |     "fiber-content": "Fiber", | ||||||
|     "carbohydrate-content": "Carbohydrate", |  | ||||||
|     "grams": "grams", |     "grams": "grams", | ||||||
|     "image": "Image", |  | ||||||
|     "ingredient": "Ingredient", |     "ingredient": "Ingredient", | ||||||
|     "ingredients": "Ingredients", |     "ingredients": "Ingredients", | ||||||
|     "instructions": "Instructions", |     "instructions": "Instructions", | ||||||
| @@ -135,27 +192,32 @@ | |||||||
|     "perform-time": "Cook Time", |     "perform-time": "Cook Time", | ||||||
|     "prep-time": "Prep Time", |     "prep-time": "Prep Time", | ||||||
|     "protein-content": "Protein", |     "protein-content": "Protein", | ||||||
|  |     "recipe-created": "Recipe created", | ||||||
|  |     "recipe-creation-failed": "Recipe creation failed", | ||||||
|  |     "recipe-deleted": "Recipe deleted", | ||||||
|     "recipe-image": "Recipe Image", |     "recipe-image": "Recipe Image", | ||||||
|  |     "recipe-image-updated": "Recipe image updated", | ||||||
|     "recipe-name": "Recipe Name", |     "recipe-name": "Recipe Name", | ||||||
|  |     "recipe-update-failed": "Recipe update failed", | ||||||
|  |     "recipe-updated": "Recipe updated", | ||||||
|     "servings": "Servings", |     "servings": "Servings", | ||||||
|     "sodium-content": "Sodium", |     "sodium-content": "Sodium", | ||||||
|     "step-index": "Step: {step}", |     "step-index": "Step: {step}", | ||||||
|     "sugar-content": "Sugar", |     "sugar-content": "Sugar", | ||||||
|     "tags": "Tags", |  | ||||||
|     "title": "Title", |     "title": "Title", | ||||||
|     "total-time": "Total Time", |     "total-time": "Total Time", | ||||||
|  |     "unable-to-delete-recipe": "Unable to Delete Recipe", | ||||||
|     "view-recipe": "View Recipe" |     "view-recipe": "View Recipe" | ||||||
|   }, |   }, | ||||||
|   "search": { |   "search": { | ||||||
|     "search-mealie": "Search Mealie (press /)", |     "and": "and", | ||||||
|     "search-placeholder": "Search...", |  | ||||||
|     "max-results": "Max Results", |  | ||||||
|     "category-filter": "Category Filter", |  | ||||||
|     "exclude": "Exclude", |     "exclude": "Exclude", | ||||||
|     "include": "Include", |     "include": "Include", | ||||||
|  |     "max-results": "Max Results", | ||||||
|     "or": "Or", |     "or": "Or", | ||||||
|     "and": "and", |  | ||||||
|     "search": "Search", |     "search": "Search", | ||||||
|  |     "search-mealie": "Search Mealie (press /)", | ||||||
|  |     "search-placeholder": "Search...", | ||||||
|     "tag-filter": "Tag Filter" |     "tag-filter": "Tag Filter" | ||||||
|   }, |   }, | ||||||
|   "settings": { |   "settings": { | ||||||
| @@ -163,10 +225,15 @@ | |||||||
|     "admin-settings": "Admin Settings", |     "admin-settings": "Admin Settings", | ||||||
|     "available-backups": "Available Backups", |     "available-backups": "Available Backups", | ||||||
|     "backup": { |     "backup": { | ||||||
|  |       "backup-created-at-response-export_path": "Backup Created at {path}", | ||||||
|  |       "backup-deleted": "Backup deleted", | ||||||
|       "backup-tag": "Backup Tag", |       "backup-tag": "Backup Tag", | ||||||
|       "create-heading": "Create a Backup", |       "create-heading": "Create a Backup", | ||||||
|  |       "error-creating-backup-see-log-file": "Error Creating Backup. See Log File", | ||||||
|       "full-backup": "Full Backup", |       "full-backup": "Full Backup", | ||||||
|       "partial-backup": "Partial Backup" |       "import-summary": "Import Summary", | ||||||
|  |       "partial-backup": "Partial Backup", | ||||||
|  |       "unable-to-delete-backup": "Unable to Delete Backup." | ||||||
|     }, |     }, | ||||||
|     "backup-and-exports": "Backups", |     "backup-and-exports": "Backups", | ||||||
|     "backup-info": "Backups are exported in standard JSON format along with all the images stored on the file system. In your backup folder you'll find a .zip file that contains all of the recipe JSON and images from the database. Additionally, if you selected a markdown file, those will also be stored in the .zip file. To import a backup, it must be located in your backups folder. Automated backups are done each day at 3:00 AM.", |     "backup-info": "Backups are exported in standard JSON format along with all the images stored on the file system. In your backup folder you'll find a .zip file that contains all of the recipe JSON and images from the database. Additionally, if you selected a markdown file, those will also be stored in the .zip file. To import a backup, it must be located in your backups folder. Automated backups are done each day at 3:00 AM.", | ||||||
| @@ -175,6 +242,7 @@ | |||||||
|     "custom-pages": "Custom Pages", |     "custom-pages": "Custom Pages", | ||||||
|     "edit-page": "Edit Page", |     "edit-page": "Edit Page", | ||||||
|     "first-day-of-week": "First day of the week", |     "first-day-of-week": "First day of the week", | ||||||
|  |     "group-settings-updated": "Group Settings Updated", | ||||||
|     "homepage": { |     "homepage": { | ||||||
|       "all-categories": "All Categories", |       "all-categories": "All Categories", | ||||||
|       "card-per-section": "Card Per Section", |       "card-per-section": "Card Per Section", | ||||||
| @@ -190,9 +258,12 @@ | |||||||
|     "migrations": "Migrations", |     "migrations": "Migrations", | ||||||
|     "new-page": "New Page", |     "new-page": "New Page", | ||||||
|     "page-name": "Page Name", |     "page-name": "Page Name", | ||||||
|  |     "pages": "Pages", | ||||||
|     "profile": "Profile", |     "profile": "Profile", | ||||||
|     "remove-existing-entries-matching-imported-entries": "Remove existing entries matching imported entries", |     "remove-existing-entries-matching-imported-entries": "Remove existing entries matching imported entries", | ||||||
|     "set-new-time": "Set New Time", |     "set-new-time": "Set New Time", | ||||||
|  |     "settings-update-failed": "Settings update failed", | ||||||
|  |     "settings-updated": "Settings updated", | ||||||
|     "site-settings": "Site Settings", |     "site-settings": "Site Settings", | ||||||
|     "theme": { |     "theme": { | ||||||
|       "accent": "Accent", |       "accent": "Accent", | ||||||
| @@ -203,6 +274,9 @@ | |||||||
|       "default-to-system": "Default to system", |       "default-to-system": "Default to system", | ||||||
|       "delete-theme": "Delete Theme", |       "delete-theme": "Delete Theme", | ||||||
|       "error": "Error", |       "error": "Error", | ||||||
|  |       "error-creating-theme-see-log-file": "Error creating theme. See log file.", | ||||||
|  |       "error-deleting-theme": "Error deleting theme", | ||||||
|  |       "error-updating-theme": "Error updating theme", | ||||||
|       "info": "Info", |       "info": "Info", | ||||||
|       "light": "Light", |       "light": "Light", | ||||||
|       "primary": "Primary", |       "primary": "Primary", | ||||||
| @@ -210,51 +284,69 @@ | |||||||
|       "select-a-theme-from-the-dropdown-or-create-a-new-theme-note-that-the-default-theme-will-be-served-to-all-users-who-have-not-set-a-theme-preference": "Select a theme from the dropdown or create a new theme. Note that the default theme will be served to all users who have not set a theme preference.", |       "select-a-theme-from-the-dropdown-or-create-a-new-theme-note-that-the-default-theme-will-be-served-to-all-users-who-have-not-set-a-theme-preference": "Select a theme from the dropdown or create a new theme. Note that the default theme will be served to all users who have not set a theme preference.", | ||||||
|       "success": "Success", |       "success": "Success", | ||||||
|       "theme": "Theme", |       "theme": "Theme", | ||||||
|  |       "theme-deleted": "Theme deleted", | ||||||
|       "theme-name": "Theme Name", |       "theme-name": "Theme Name", | ||||||
|       "theme-name-is-required": "Theme Name is required.", |       "theme-name-is-required": "Theme Name is required.", | ||||||
|  |       "theme-saved": "Theme Saved", | ||||||
|       "theme-settings": "Theme Settings", |       "theme-settings": "Theme Settings", | ||||||
|  |       "theme-updated": "Theme updated", | ||||||
|       "warning": "Warning" |       "warning": "Warning" | ||||||
|     }, |     }, | ||||||
|  |     "toolbox": { | ||||||
|  |       "assign-all": "Assign All", | ||||||
|  |       "bulk-assign": "Bulk Assign", | ||||||
|  |       "new-name": "New Name", | ||||||
|  |       "no-unused-items": "No Unused Items", | ||||||
|  |       "recipes-affected": "No Recipes Affected|One Recipe Affected|{count} Recipes Affected", | ||||||
|  |       "remove-unused": "Remove Unused", | ||||||
|  |       "title-case-all": "Title Case All", | ||||||
|  |       "toolbox": "Toolbox" | ||||||
|  |     }, | ||||||
|     "webhooks": { |     "webhooks": { | ||||||
|       "meal-planner-webhooks": "Meal Planner Webhooks", |       "meal-planner-webhooks": "Meal Planner Webhooks", | ||||||
|       "test-webhooks": "Test Webhooks", |       "test-webhooks": "Test Webhooks", | ||||||
|       "the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "The URLs listed below will receive webhooks containing the recipe data for the meal plan on it's scheduled day. Currently Webhooks will execute at", |       "the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "The URLs listed below will receive webhooks containing the recipe data for the meal plan on it's scheduled day. Currently Webhooks will execute at", | ||||||
|       "webhook-url": "Webhook URL" |       "webhook-url": "Webhook URL" | ||||||
|     }, |  | ||||||
|     "toolbox": { |  | ||||||
|       "toolbox": "Toolbox", |  | ||||||
|       "new-name": "New Name", |  | ||||||
|       "recipes-effected": "Recipes Effected", |  | ||||||
|       "title-case-all": "Title Case All", |  | ||||||
|       "no-unused-items": "No Unused Items", |  | ||||||
|       "remove-unused": "Remove Unused", |  | ||||||
|       "assign-all": "Assign All", |  | ||||||
|       "bulk-assign": "Bulk Assign" |  | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|  |   "signup": { | ||||||
|  |     "display-name": "Display Name", | ||||||
|  |     "error-signing-up": "Error Signing Up", | ||||||
|  |     "sign-up": "Sign Up", | ||||||
|  |     "sign-up-link-created": "Sign up link created", | ||||||
|  |     "sign-up-link-creation-failed": "Sign up link creation failed", | ||||||
|  |     "sign-up-links": "Sign Up Links", | ||||||
|  |     "sign-up-token-deleted": "Sign Up Token Deleted", | ||||||
|  |     "sign-up-token-deletion-failed": "Sign up token deletion failed", | ||||||
|  |     "welcome-to-mealie": "Welcome to Mealie! To become a user of this instance you are required to have a valid invitation link. If you haven't recieved an invitation you are unable to sign-up. To recieve a link, contact the sites administrator." | ||||||
|  |   }, | ||||||
|  |   "tag": { | ||||||
|  |     "tag-created": "Tag created", | ||||||
|  |     "tag-creation-failed": "Tag creation failed", | ||||||
|  |     "tag-deleted": "Tag deleted", | ||||||
|  |     "tag-deletion-failed": "Tag deletion failed", | ||||||
|  |     "tag-update-failed": "Tag update failed", | ||||||
|  |     "tag-updated": "Tag updated", | ||||||
|  |     "tags": "Tags" | ||||||
|  |   }, | ||||||
|   "user": { |   "user": { | ||||||
|     "admin": "Admin", |     "admin": "Admin", | ||||||
|     "are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?", |  | ||||||
|     "are-you-sure-you-want-to-delete-the-link": "Are you sure you want to delete the link <b>{link}<b/>?", |     "are-you-sure-you-want-to-delete-the-link": "Are you sure you want to delete the link <b>{link}<b/>?", | ||||||
|     "are-you-sure-you-want-to-delete-the-user": "Are you sure you want to delete the user <b>{activeName} ID: {activeId}<b/>?", |     "are-you-sure-you-want-to-delete-the-user": "Are you sure you want to delete the user <b>{activeName} ID: {activeId}<b/>?", | ||||||
|     "confirm-group-deletion": "Confirm Group Deletion", |  | ||||||
|     "confirm-link-deletion": "Confirm Link Deletion", |     "confirm-link-deletion": "Confirm Link Deletion", | ||||||
|     "confirm-password": "Confirm Password", |     "confirm-password": "Confirm Password", | ||||||
|     "confirm-user-deletion": "Confirm User Deletion", |     "confirm-user-deletion": "Confirm User Deletion", | ||||||
|     "could-not-validate-credentials": "Could Not Validate Credentials", |     "could-not-validate-credentials": "Could Not Validate Credentials", | ||||||
|     "create-group": "Create Group", |  | ||||||
|     "create-link": "Create Link", |     "create-link": "Create Link", | ||||||
|     "create-user": "Create User", |     "create-user": "Create User", | ||||||
|     "current-password": "Current Password", |     "current-password": "Current Password", | ||||||
|     "e-mail-must-be-valid": "E-mail must be valid", |     "e-mail-must-be-valid": "E-mail must be valid", | ||||||
|     "edit-user": "Edit User", |     "edit-user": "Edit User", | ||||||
|     "email": "Email", |     "email": "Email", | ||||||
|  |     "error-cannot-delete-super-user": "Error! Cannot Delete Super User", | ||||||
|  |     "existing-password-does-not-match": "Existing password does not match", | ||||||
|     "full-name": "Full Name", |     "full-name": "Full Name", | ||||||
|     "group": "Group", |     "incorrect-username-or-password": "Incorrect username or password", | ||||||
|     "group-id-with-value": "Group ID: {groupID}", |  | ||||||
|     "group-name": "Group Name", |  | ||||||
|     "groups": "Groups", |  | ||||||
|     "groups-can-only-be-set-by-administrators": "Groups can only be set by administrators", |  | ||||||
|     "link-id": "Link ID", |     "link-id": "Link ID", | ||||||
|     "link-name": "Link Name", |     "link-name": "Link Name", | ||||||
|     "login": "Login", |     "login": "Login", | ||||||
| @@ -262,20 +354,29 @@ | |||||||
|     "new-password": "New Password", |     "new-password": "New Password", | ||||||
|     "new-user": "New User", |     "new-user": "New User", | ||||||
|     "password": "Password", |     "password": "Password", | ||||||
|  |     "password-has-been-reset-to-the-default-password": "Password has been reset to the default password", | ||||||
|     "password-must-match": "Password must match", |     "password-must-match": "Password must match", | ||||||
|  |     "password-reset-failed": "Password reset failed", | ||||||
|  |     "password-updated": "Password updated", | ||||||
|     "reset-password": "Reset Password", |     "reset-password": "Reset Password", | ||||||
|     "sign-in": "Sign in", |     "sign-in": "Sign in", | ||||||
|     "sign-up-links": "Sign Up Links", |  | ||||||
|     "total-mealplans": "Total MealPlans", |     "total-mealplans": "Total MealPlans", | ||||||
|     "total-users": "Total Users", |     "total-users": "Total Users", | ||||||
|     "upload-photo": "Upload Photo", |     "upload-photo": "Upload Photo", | ||||||
|     "use-8-characters-or-more-for-your-password": "Use 8 characters or more for your password", |     "use-8-characters-or-more-for-your-password": "Use 8 characters or more for your password", | ||||||
|     "user-group": "User Group", |     "user-created": "User created", | ||||||
|  |     "user-creation-failed": "User creation failed", | ||||||
|  |     "user-deleted": "User deleted", | ||||||
|     "user-id": "User ID", |     "user-id": "User ID", | ||||||
|     "user-id-with-value": "User ID: {id}", |     "user-id-with-value": "User ID: {id}", | ||||||
|     "user-password": "User Password", |     "user-password": "User Password", | ||||||
|  |     "user-successfully-logged-in": "User Successfully Logged In", | ||||||
|  |     "user-update-failed": "User update failed", | ||||||
|  |     "user-updated": "User updated", | ||||||
|     "users": "Users", |     "users": "Users", | ||||||
|     "webhook-time": "Webhook Time", |     "webhook-time": "Webhook Time", | ||||||
|     "webhooks-enabled": "Webhooks Enabled" |     "webhooks-enabled": "Webhooks Enabled", | ||||||
|  |     "you-are-not-allowed-to-create-a-user": "You are not allowed to create a user", | ||||||
|  |     "you-are-not-allowed-to-delete-this-user": "You are not allowed to delete this user" | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -23,11 +23,11 @@ | |||||||
|       <v-card-actions> |       <v-card-actions> | ||||||
|         <v-spacer></v-spacer> |         <v-spacer></v-spacer> | ||||||
|         <TheDownloadBtn |         <TheDownloadBtn | ||||||
|           button-text="Download Recipe JSON" |           :button-text="$t('about.download-recipe-json')" | ||||||
|           download-url="/api/debug/last-recipe-json" |           download-url="/api/debug/last-recipe-json" | ||||||
|         /> |         /> | ||||||
|         <TheDownloadBtn |         <TheDownloadBtn | ||||||
|           button-text="Download Log" |           :button-text="$t('about.download-log')" | ||||||
|           download-url="/api/debug/log" |           download-url="/api/debug/log" | ||||||
|         /> |         /> | ||||||
|       </v-card-actions> |       </v-card-actions> | ||||||
|   | |||||||
| @@ -62,17 +62,21 @@ export default { | |||||||
|     }, |     }, | ||||||
|     async importBackup(data) { |     async importBackup(data) { | ||||||
|       this.$emit("loading"); |       this.$emit("loading"); | ||||||
|       let response = await api.backups.import(data.name, data); |       const response = await api.backups.import(data.name, data); | ||||||
|  |       if(response) { | ||||||
|  |         let importData = response.data; | ||||||
|  |         this.$emit("finished", importData); | ||||||
|  |       } else { | ||||||
|  |         this.$emit("finished"); | ||||||
|  |       } | ||||||
|  |  | ||||||
|       let importData = response.data; |  | ||||||
|  |  | ||||||
|       this.$emit("finished", importData); |  | ||||||
|     }, |     }, | ||||||
|     deleteBackup(data) { |     async deleteBackup(data) { | ||||||
|       this.$emit("loading"); |       this.$emit("loading"); | ||||||
|  |  | ||||||
|       api.backups.delete(data.name); |       if (await api.backups.delete(data.name)) { | ||||||
|       this.selectedBackup = null; |         this.selectedBackup = null; | ||||||
|  |       } | ||||||
|       this.backupLoading = false; |       this.backupLoading = false; | ||||||
|  |  | ||||||
|       this.$emit("finished"); |       this.$emit("finished"); | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ export default { | |||||||
|         }, |         }, | ||||||
|         pages: { |         pages: { | ||||||
|           value: true, |           value: true, | ||||||
|           text: "Pages", |           text: this.$t("settings.pages"), | ||||||
|         }, |         }, | ||||||
|         themes: { |         themes: { | ||||||
|           value: true, |           value: true, | ||||||
|   | |||||||
| @@ -20,11 +20,11 @@ | |||||||
|         <v-card-text class="mt-n4"> |         <v-card-text class="mt-n4"> | ||||||
|           <v-row> |           <v-row> | ||||||
|             <v-col sm="4"> |             <v-col sm="4"> | ||||||
|               <p>{{ $t("general.options") }}:</p> |               <p>{{ $t("general.options") }}</p> | ||||||
|               <ImportOptions @update-options="updateOptions" class="mt-5" /> |               <ImportOptions @update-options="updateOptions" class="mt-5" /> | ||||||
|             </v-col> |             </v-col> | ||||||
|             <v-col> |             <v-col> | ||||||
|               <p>{{ $t("general.templates") }}:</p> |               <p>{{ $t("general.templates") }}</p> | ||||||
|               <v-checkbox |               <v-checkbox | ||||||
|                 v-for="template in availableTemplates" |                 v-for="template in availableTemplates" | ||||||
|                 :key="template" |                 :key="template" | ||||||
| @@ -97,10 +97,11 @@ export default { | |||||||
|         templates: this.selectedTemplates, |         templates: this.selectedTemplates, | ||||||
|       }; |       }; | ||||||
|  |  | ||||||
|       await api.backups.create(data); |       if (await api.backups.create(data)) { | ||||||
|  |         this.$emit("created"); | ||||||
|  |       } | ||||||
|       this.loading = false; |       this.loading = false; | ||||||
|  |  | ||||||
|       this.$emit("created"); |  | ||||||
|     }, |     }, | ||||||
|     appendTemplate(templateName) { |     appendTemplate(templateName) { | ||||||
|       if (this.selectedTemplates.includes(templateName)) { |       if (this.selectedTemplates.includes(templateName)) { | ||||||
|   | |||||||
| @@ -2,9 +2,9 @@ | |||||||
|   <div> |   <div> | ||||||
|     <ConfirmationDialog |     <ConfirmationDialog | ||||||
|       ref="deleteGroupConfirm" |       ref="deleteGroupConfirm" | ||||||
|       :title="$t('user.confirm-group-deletion')" |       :title="$t('group.confirm-group-deletion')" | ||||||
|       :message=" |       :message=" | ||||||
|         $t('user.are-you-sure-you-want-to-delete-the-group', { |         $t('group.are-you-sure-you-want-to-delete-the-group', { | ||||||
|           groupName: group.name, |           groupName: group.name, | ||||||
|         }) |         }) | ||||||
|       " |       " | ||||||
| @@ -18,7 +18,7 @@ | |||||||
|         <v-card-title class="py-1">{{ group.name }}</v-card-title> |         <v-card-title class="py-1">{{ group.name }}</v-card-title> | ||||||
|         <v-divider></v-divider> |         <v-divider></v-divider> | ||||||
|         <v-subheader>{{ |         <v-subheader>{{ | ||||||
|           $t("user.group-id-with-value", { groupID: group.id }) |           $t("group.group-id-with-value", { groupID: group.id }) | ||||||
|         }}</v-subheader> |         }}</v-subheader> | ||||||
|         <v-list-item-group color="primary"> |         <v-list-item-group color="primary"> | ||||||
|           <v-list-item v-for="property in groupProps" :key="property.text"> |           <v-list-item v-for="property in groupProps" :key="property.text"> | ||||||
| @@ -91,8 +91,9 @@ export default { | |||||||
|       this.$refs.deleteGroupConfirm.open(); |       this.$refs.deleteGroupConfirm.open(); | ||||||
|     }, |     }, | ||||||
|     async deleteGroup() { |     async deleteGroup() { | ||||||
|       await api.groups.delete(this.group.id); |       if (await api.groups.delete(this.group.id)) { | ||||||
|       this.$emit(RENDER_EVENT); |         this.$emit(RENDER_EVENT); | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     closeGroupDelete() { |     closeGroupDelete() { | ||||||
|       console.log("Close Delete"); |       console.log("Close Delete"); | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ | |||||||
|               v-bind="attrs" |               v-bind="attrs" | ||||||
|               v-on="on" |               v-on="on" | ||||||
|             > |             > | ||||||
|               {{ $t("user.create-group") }} |               {{ $t("group.create-group") }} | ||||||
|             </v-btn> |             </v-btn> | ||||||
|           </template> |           </template> | ||||||
|           <v-card> |           <v-card> | ||||||
| @@ -34,7 +34,7 @@ | |||||||
|               </v-icon> |               </v-icon> | ||||||
|  |  | ||||||
|               <v-toolbar-title class="headline"> |               <v-toolbar-title class="headline"> | ||||||
|                 {{ $t("user.create-group") }} |                 {{ $t("group.create-group") }} | ||||||
|               </v-toolbar-title> |               </v-toolbar-title> | ||||||
|  |  | ||||||
|               <v-spacer></v-spacer> |               <v-spacer></v-spacer> | ||||||
| @@ -43,7 +43,7 @@ | |||||||
|               <v-card-text> |               <v-card-text> | ||||||
|                 <v-text-field |                 <v-text-field | ||||||
|                   v-model="newGroupName" |                   v-model="newGroupName" | ||||||
|                   :label="$t('user.group-name')" |                   :label="$t('group.group-name')" | ||||||
|                   :rules="[existsRule]" |                   :rules="[existsRule]" | ||||||
|                 ></v-text-field> |                 ></v-text-field> | ||||||
|               </v-card-text> |               </v-card-text> | ||||||
| @@ -104,12 +104,11 @@ export default { | |||||||
|   methods: { |   methods: { | ||||||
|     async createGroup() { |     async createGroup() { | ||||||
|       this.groupLoading = true; |       this.groupLoading = true; | ||||||
|       let response = await api.groups.create(this.newGroupName); |       if (await api.groups.create(this.newGroupName)) { | ||||||
|       if (response.created) { |  | ||||||
|         this.groupLoading = false; |  | ||||||
|         this.groupDialog = false; |         this.groupDialog = false; | ||||||
|         this.$store.dispatch("requestAllGroups"); |         this.$store.dispatch("requestAllGroups"); | ||||||
|       } |       } | ||||||
|  |       this.groupLoading = false; | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| <template> | <template> | ||||||
|   <v-card outlined class="mt-n1"> |   <v-card outlined class="mt-n1"> | ||||||
|     <ConfirmationDialog |     <ConfirmationDialog | ||||||
|       ref="deleteUserDialog" |       ref="deleteTokenDialog" | ||||||
|       :title="$t('user.confirm-link-deletion')" |       :title="$t('user.confirm-link-deletion')" | ||||||
|       :message=" |       :message=" | ||||||
|         $t('user.are-you-sure-you-want-to-delete-the-link', { |         $t('user.are-you-sure-you-want-to-delete-the-link', { | ||||||
| @@ -9,7 +9,7 @@ | |||||||
|         }) |         }) | ||||||
|       " |       " | ||||||
|       icon="mdi-alert" |       icon="mdi-alert" | ||||||
|       @confirm="deleteUser" |       @confirm="deleteToken" | ||||||
|       :width="450" |       :width="450" | ||||||
|       @close="closeDelete" |       @close="closeDelete" | ||||||
|     /> |     /> | ||||||
| @@ -18,7 +18,7 @@ | |||||||
|         mdi-link-variant |         mdi-link-variant | ||||||
|       </v-icon> |       </v-icon> | ||||||
|       <v-toolbar-title class="headine"> |       <v-toolbar-title class="headine"> | ||||||
|         {{ $t("user.sign-up-links") }} |         {{ $t("signup.sign-up-links") }} | ||||||
|       </v-toolbar-title> |       </v-toolbar-title> | ||||||
|  |  | ||||||
|       <v-spacer> </v-spacer> |       <v-spacer> </v-spacer> | ||||||
| @@ -181,9 +181,10 @@ export default { | |||||||
|       this.links = await api.signUps.getAll(); |       this.links = await api.signUps.getAll(); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     async deleteUser() { |     async deleteToken() { | ||||||
|       await api.signUps.deleteToken(this.activeId); |       if (await api.signUps.deleteToken(this.activeId)) { | ||||||
|       this.initialize(); |         this.initialize(); | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     editItem(item) { |     editItem(item) { | ||||||
| @@ -197,7 +198,7 @@ export default { | |||||||
|       this.activeName = item.name; |       this.activeName = item.name; | ||||||
|       this.editedIndex = this.links.indexOf(item); |       this.editedIndex = this.links.indexOf(item); | ||||||
|       this.editedItem = Object.assign({}, item); |       this.editedItem = Object.assign({}, item); | ||||||
|       this.$refs.deleteUserDialog.open(); |       this.$refs.deleteTokenDialog.open(); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     deleteItemConfirm() { |     deleteItemConfirm() { | ||||||
|   | |||||||
| @@ -72,7 +72,7 @@ | |||||||
|                     dense |                     dense | ||||||
|                     v-model="editedItem.group" |                     v-model="editedItem.group" | ||||||
|                     :items="existingGroups" |                     :items="existingGroups" | ||||||
|                     :label="$t('user.user-group')" |                     :label="$t('group.user-group')" | ||||||
|                   ></v-select> |                   ></v-select> | ||||||
|                 </v-col> |                 </v-col> | ||||||
|                 <v-col cols="12" sm="12" md="6" v-if="showPassword"> |                 <v-col cols="12" sm="12" md="6" v-if="showPassword"> | ||||||
| @@ -94,7 +94,7 @@ | |||||||
|  |  | ||||||
|             <v-card-actions> |             <v-card-actions> | ||||||
|               <v-btn color="info" text @click="resetPassword"> |               <v-btn color="info" text @click="resetPassword"> | ||||||
|                 Reset Password |                 {{$t('user.reset-password')}} | ||||||
|               </v-btn> |               </v-btn> | ||||||
|               <v-spacer></v-spacer> |               <v-spacer></v-spacer> | ||||||
|               <v-btn color="grey" text @click="close"> |               <v-btn color="grey" text @click="close"> | ||||||
| @@ -165,7 +165,7 @@ export default { | |||||||
|         }, |         }, | ||||||
|         { text: this.$t("user.full-name"), value: "fullName" }, |         { text: this.$t("user.full-name"), value: "fullName" }, | ||||||
|         { text: this.$t("user.email"), value: "email" }, |         { text: this.$t("user.email"), value: "email" }, | ||||||
|         { text: this.$t("user.group"), value: "group" }, |         { text: this.$t("group.group"), value: "group" }, | ||||||
|         { text: this.$t("user.admin"), value: "admin" }, |         { text: this.$t("user.admin"), value: "admin" }, | ||||||
|         { text: "", value: "actions", sortable: false, align: "center" }, |         { text: "", value: "actions", sortable: false, align: "center" }, | ||||||
|       ], |       ], | ||||||
| @@ -223,8 +223,9 @@ export default { | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     async deleteUser() { |     async deleteUser() { | ||||||
|       await api.users.delete(this.activeId); |       if (await api.users.delete(this.activeId)) { | ||||||
|       this.initialize(); |         this.initialize(); | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     editItem(item) { |     editItem(item) { | ||||||
| @@ -264,17 +265,27 @@ export default { | |||||||
|  |  | ||||||
|     async save() { |     async save() { | ||||||
|       if (this.editedIndex > -1) { |       if (this.editedIndex > -1) { | ||||||
|         await api.users.update(this.editedItem); |         this.updateUser(); | ||||||
|         this.close(); |  | ||||||
|       } else if (this.$refs.newUser.validate()) { |       } else if (this.$refs.newUser.validate()) { | ||||||
|         await api.users.create(this.editedItem); |         this.createUser(); | ||||||
|         this.close(); |  | ||||||
|       } |       } | ||||||
|       await this.initialize(); |       await this.initialize(); | ||||||
|     }, |     }, | ||||||
|     resetPassword() { |     resetPassword() { | ||||||
|       api.users.resetPassword(this.editedItem.id); |       api.users.resetPassword(this.editedItem.id); | ||||||
|     }, |     }, | ||||||
|  |      | ||||||
|  |     async createUser() { | ||||||
|  |       if(await api.users.create(this.editedItem)) { | ||||||
|  |         this.close(); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     async updateUser() { | ||||||
|  |       if(await api.users.update(this.editedItem)) { | ||||||
|  |         this.close(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -16,12 +16,12 @@ | |||||||
|         </v-tab> |         </v-tab> | ||||||
|  |  | ||||||
|         <v-tab> |         <v-tab> | ||||||
|           {{ $t("user.sign-up-links") }} |           {{ $t("signup.sign-up-links") }} | ||||||
|           <v-icon>mdi-account-plus-outline</v-icon> |           <v-icon>mdi-account-plus-outline</v-icon> | ||||||
|         </v-tab> |         </v-tab> | ||||||
|  |  | ||||||
|         <v-tab> |         <v-tab> | ||||||
|           {{ $t("user.groups") }} |           {{ $t("group.groups") }} | ||||||
|           <v-icon>mdi-account-group</v-icon> |           <v-icon>mdi-account-group</v-icon> | ||||||
|         </v-tab> |         </v-tab> | ||||||
|       </v-tabs> |       </v-tabs> | ||||||
|   | |||||||
| @@ -135,9 +135,10 @@ export default { | |||||||
|       this.groupSettings.webhookUrls.splice(index, 1); |       this.groupSettings.webhookUrls.splice(index, 1); | ||||||
|     }, |     }, | ||||||
|     async saveGroupSettings() { |     async saveGroupSettings() { | ||||||
|       await api.groups.update(this.groupSettings); |       if (await api.groups.update(this.groupSettings)) { | ||||||
|       await this.$store.dispatch("requestCurrentGroup"); |         await this.$store.dispatch("requestCurrentGroup"); | ||||||
|       this.getSiteSettings(); |         this.getSiteSettings(); | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     testWebhooks() { |     testWebhooks() { | ||||||
|       api.settings.testWebhooks(); |       api.settings.testWebhooks(); | ||||||
|   | |||||||
| @@ -86,9 +86,10 @@ export default { | |||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     deleteMigration(file_name) { |     async deleteMigration(file_name) { | ||||||
|       api.migrations.delete(this.folder, file_name); |       if (await api.migrations.delete(this.folder, file_name)) { | ||||||
|       this.$emit("refresh"); |         this.$emit("refresh"); | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     async importMigration(file_name) { |     async importMigration(file_name) { | ||||||
|       this.loading = true; |       this.loading = true; | ||||||
|   | |||||||
| @@ -55,11 +55,11 @@ | |||||||
|                 > |                 > | ||||||
|                 </v-text-field> |                 </v-text-field> | ||||||
|                 <v-text-field |                 <v-text-field | ||||||
|                   :label="$t('user.group')" |                   :label="$t('group.group')" | ||||||
|                   readonly |                   readonly | ||||||
|                   v-model="user.group" |                   v-model="user.group" | ||||||
|                   persistent-hint |                   persistent-hint | ||||||
|                   :hint="$t('user.groups-can-only-be-set-by-administrators')" |                   :hint="$t('group.groups-can-only-be-set-by-administrators')" | ||||||
|                 > |                 > | ||||||
|                 </v-text-field> |                 </v-text-field> | ||||||
|               </v-form> |               </v-form> | ||||||
| @@ -201,11 +201,13 @@ export default { | |||||||
|     }, |     }, | ||||||
|     async updateUser() { |     async updateUser() { | ||||||
|       this.loading = true; |       this.loading = true; | ||||||
|       let newKey = await api.users.update(this.user); |       const response = await api.users.update(this.user); | ||||||
|       this.$store.commit("setToken", newKey.access_token); |       if(response) { | ||||||
|       this.refreshProfile(); |         this.$store.commit("setToken", response.data.access_token); | ||||||
|       this.loading = false; |         this.refreshProfile(); | ||||||
|       this.$store.dispatch("requestUserData"); |         this.loading = false; | ||||||
|  |         this.$store.dispatch("requestUserData"); | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     async changePassword() { |     async changePassword() { | ||||||
|       this.paswordLoading = true; |       this.paswordLoading = true; | ||||||
| @@ -215,7 +217,9 @@ export default { | |||||||
|       }; |       }; | ||||||
|  |  | ||||||
|       if (this.$refs.passChange.validate()) { |       if (this.$refs.passChange.validate()) { | ||||||
|         await api.users.changePassword(this.user.id, data); |         if (await api.users.changePassword(this.user.id, data)) { | ||||||
|  |           this.$emit("refresh"); | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|       this.paswordLoading = false; |       this.paswordLoading = false; | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -82,14 +82,18 @@ export default { | |||||||
|       this.$refs.categoryFormSelector.setInit(this.page.categories); |       this.$refs.categoryFormSelector.setInit(this.page.categories); | ||||||
|     }, |     }, | ||||||
|     async submitForm() { |     async submitForm() { | ||||||
|  |       let response; | ||||||
|       if (this.create) { |       if (this.create) { | ||||||
|         await api.siteSettings.createPage(this.page); |         response = await api.siteSettings.createPage(this.page); | ||||||
|       } else { |       } else { | ||||||
|         await api.siteSettings.updatePage(this.page); |         response = await api.siteSettings.updatePage(this.page); | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       if (response) { | ||||||
|  |         this.pageDialog = false; | ||||||
|  |         this.page.categories = []; | ||||||
|  |         this.$emit(NEW_PAGE_EVENT); | ||||||
|       } |       } | ||||||
|       this.pageDialog = false; |  | ||||||
|       this.page.categories = []; |  | ||||||
|       this.$emit(NEW_PAGE_EVENT); |  | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -109,9 +109,10 @@ export default { | |||||||
|         element.position = index; |         element.position = index; | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|       await api.siteSettings.updateAllPages(this.customPages); |       if (await api.siteSettings.updateAllPages(this.customPages)) { | ||||||
|  |         this.getPages(); | ||||||
|  |       } | ||||||
|  |  | ||||||
|       this.getPages(); |  | ||||||
|     }, |     }, | ||||||
|     editPage(index) { |     editPage(index) { | ||||||
|       this.editPageData.data = this.customPages[index]; |       this.editPageData.data = this.customPages[index]; | ||||||
|   | |||||||
| @@ -223,8 +223,9 @@ export default { | |||||||
|       this.settings.categories.splice(index, 1); |       this.settings.categories.splice(index, 1); | ||||||
|     }, |     }, | ||||||
|     async saveSettings() { |     async saveSettings() { | ||||||
|       await api.siteSettings.update(this.settings); |       if (await api.siteSettings.update(this.settings)) { | ||||||
|       this.getOptions(); |         this.getOptions(); | ||||||
|  |       }  | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -70,11 +70,11 @@ export default { | |||||||
|     }, |     }, | ||||||
|     async deleteSelectedTheme() { |     async deleteSelectedTheme() { | ||||||
|       //Delete Theme from DB |       //Delete Theme from DB | ||||||
|       await api.themes.delete(this.theme.name); |       if (await api.themes.delete(this.theme.name)) { | ||||||
|  |         //Get the new list of available from DB | ||||||
|       //Get the new list of available from DB |         this.availableThemes = await api.themes.requestAll(); | ||||||
|       this.availableThemes = await api.themes.requestAll(); |         this.$emit(DELETE_EVENT); | ||||||
|       this.$emit(DELETE_EVENT); |       } | ||||||
|     }, |     }, | ||||||
|     async saveThemes() { |     async saveThemes() { | ||||||
|       this.$store.commit("setTheme", this.theme); |       this.$store.commit("setTheme", this.theme); | ||||||
|   | |||||||
| @@ -171,9 +171,11 @@ export default { | |||||||
|      * Create the new Theme and select it. |      * Create the new Theme and select it. | ||||||
|      */ |      */ | ||||||
|     async appendTheme(NewThemeDialog) { |     async appendTheme(NewThemeDialog) { | ||||||
|       await api.themes.create(NewThemeDialog); |       const response = await api.themes.create(NewThemeDialog); | ||||||
|       this.availableThemes.push(NewThemeDialog); |       if (response) { | ||||||
|       this.$store.commit("setTheme", NewThemeDialog); |         this.availableThemes.push(NewThemeDialog); | ||||||
|  |         this.$store.commit("setTheme", NewThemeDialog); | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     setStoresDarkMode() { |     setStoresDarkMode() { | ||||||
|       this.$store.commit("setDarkMode", this.selectedDarkMode); |       this.$store.commit("setDarkMode", this.selectedDarkMode); | ||||||
| @@ -181,8 +183,8 @@ export default { | |||||||
|     /** |     /** | ||||||
|      * This will save the current colors and make the selected theme live. |      * This will save the current colors and make the selected theme live. | ||||||
|      */ |      */ | ||||||
|     async saveThemes() { |     saveThemes() { | ||||||
|       await api.themes.update( |       api.themes.update( | ||||||
|         this.selectedTheme.name, |         this.selectedTheme.name, | ||||||
|         this.selectedTheme.colors |         this.selectedTheme.colors | ||||||
|       ); |       ); | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
|       ref="assignDialog" |       ref="assignDialog" | ||||||
|       title-icon="mdi-tag" |       title-icon="mdi-tag" | ||||||
|       color="primary" |       color="primary" | ||||||
|       title="Bulk Assign" |       :title="$t('settings.toolbox.bulk-assign')" | ||||||
|       :loading="loading" |       :loading="loading" | ||||||
|       modal-width="700" |       modal-width="700" | ||||||
|       :top="true" |       :top="true" | ||||||
| @@ -13,7 +13,7 @@ | |||||||
|         <v-text-field |         <v-text-field | ||||||
|           v-model="search" |           v-model="search" | ||||||
|           autocomplete="off" |           autocomplete="off" | ||||||
|           label="Keyword" |           :label="$t('general.keyword')" | ||||||
|         ></v-text-field> |         ></v-text-field> | ||||||
|         <CategoryTagSelector |         <CategoryTagSelector | ||||||
|           :tag-selector="false" |           :tag-selector="false" | ||||||
| @@ -44,7 +44,7 @@ | |||||||
|         <v-card-title class="headline"> </v-card-title> |         <v-card-title class="headline"> </v-card-title> | ||||||
|         <CardSection |         <CardSection | ||||||
|           class="px-2 pb-2" |           class="px-2 pb-2" | ||||||
|           :title="`${results.length || 0} Recipes Effected`" |           :title="$tc('settings.toolbox.recipes-affected', results.length || 0)" | ||||||
|           :mobile-cards="true" |           :mobile-cards="true" | ||||||
|           :recipes="results" |           :recipes="results" | ||||||
|           :single-column="true" |           :single-column="true" | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ | |||||||
|       :title=" |       :title=" | ||||||
|         $t('general.delete') + |         $t('general.delete') + | ||||||
|           ' ' + |           ' ' + | ||||||
|           (isTags ? $t('recipe.tags') : $t('recipe.categories')) |           (isTags ? $t('tag.tags') : $t('recipe.categories')) | ||||||
|       " |       " | ||||||
|       :loading="loading" |       :loading="loading" | ||||||
|       modal-width="400" |       modal-width="400" | ||||||
|   | |||||||
| @@ -18,8 +18,7 @@ | |||||||
|       </v-form> |       </v-form> | ||||||
|       <template slot="below-actions"> |       <template slot="below-actions"> | ||||||
|         <v-card-title class="headline"> |         <v-card-title class="headline"> | ||||||
|           {{ renameTarget.recipes.length || 0 }} |           {{ $tc("settings.toolbox.recipes-affected", renameTarget.recipes.length || 0) }} | ||||||
|           {{ $t("settings.toolbox.recipes-effected") }} |  | ||||||
|         </v-card-title> |         </v-card-title> | ||||||
|         <MobileRecipeCard |         <MobileRecipeCard | ||||||
|           class="ml-2 mr-2 mt-2 mb-2" |           class="ml-2 mr-2 mt-2 mb-2" | ||||||
| @@ -94,10 +93,10 @@ | |||||||
|               <v-card-title class="py-1">{{ item.name }}</v-card-title> |               <v-card-title class="py-1">{{ item.name }}</v-card-title> | ||||||
|               <v-spacer></v-spacer> |               <v-spacer></v-spacer> | ||||||
|               <v-btn small text color="info" @click="openEditDialog(item)"> |               <v-btn small text color="info" @click="openEditDialog(item)"> | ||||||
|                 Edit |                 {{$t('general.edit')}} | ||||||
|               </v-btn> |               </v-btn> | ||||||
|               <v-btn small text color="error" @click="deleteItem(item.slug)" |               <v-btn small text color="error" @click="deleteItem(item.slug)"> | ||||||
|                 >Delete |                 {{$t('general.delete')}} | ||||||
|               </v-btn> |               </v-btn> | ||||||
|             </v-card-actions> |             </v-card-actions> | ||||||
|           </v-card> |           </v-card> | ||||||
| @@ -177,7 +176,7 @@ export default { | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       this.renameTarget = { |       this.renameTarget = { | ||||||
|         title: `Rename ${item.name}`, |         title:this.$t('general.rename-object', [item.name]), | ||||||
|         name: item.name, |         name: item.name, | ||||||
|         slug: item.slug, |         slug: item.slug, | ||||||
|         newName: "", |         newName: "", | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ | |||||||
|         </v-tab> |         </v-tab> | ||||||
|  |  | ||||||
|         <v-tab> |         <v-tab> | ||||||
|           {{ $t("recipe.tags") }} |           {{ $t("tag.tags") }} | ||||||
|           <v-icon>mdi-tag-multiple-outline</v-icon> |           <v-icon>mdi-tag-multiple-outline</v-icon> | ||||||
|         </v-tab> |         </v-tab> | ||||||
|       </v-tabs> |       </v-tabs> | ||||||
|   | |||||||
| @@ -128,8 +128,9 @@ export default { | |||||||
|       this.requestMeals(); |       this.requestMeals(); | ||||||
|     }, |     }, | ||||||
|     async deletePlan(id) { |     async deletePlan(id) { | ||||||
|       await api.mealPlans.delete(id); |       if (await api.mealPlans.delete(id)) { | ||||||
|       this.requestMeals(); |         this.requestMeals(); | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     openShoppingList(id) { |     openShoppingList(id) { | ||||||
|       this.$refs.shoppingList.openDialog(id); |       this.$refs.shoppingList.openDialog(id); | ||||||
|   | |||||||
| @@ -52,6 +52,7 @@ | |||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
|  | import utils from "@/utils"; | ||||||
| export default { | export default { | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
| @@ -60,6 +61,9 @@ export default { | |||||||
|   }, |   }, | ||||||
|   async mounted() { |   async mounted() { | ||||||
|     this.mealPlan = await api.mealPlans.thisWeek(); |     this.mealPlan = await api.mealPlans.thisWeek(); | ||||||
|  |     if(!this.mealPlan) { | ||||||
|  |       utils.notify.warning(this.$t('meal-plan.no-meal-plan-defined-yet')) | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     getOrder(index) { |     getOrder(index) { | ||||||
|   | |||||||
| @@ -101,7 +101,7 @@ export default { | |||||||
|         let slug = await api.recipes.create(this.recipeDetails); |         let slug = await api.recipes.create(this.recipeDetails); | ||||||
|  |  | ||||||
|         if (this.fileObject) { |         if (this.fileObject) { | ||||||
|           await api.recipes.updateImage(slug, this.fileObject); |           api.recipes.updateImage(slug, this.fileObject, true); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.isLoading = false; |         this.isLoading = false; | ||||||
|   | |||||||
| @@ -80,6 +80,8 @@ import RecipeEditor from "@/components/Recipe/RecipeEditor"; | |||||||
| import RecipeTimeCard from "@/components/Recipe/RecipeTimeCard.vue"; | import RecipeTimeCard from "@/components/Recipe/RecipeTimeCard.vue"; | ||||||
| import EditorButtonRow from "@/components/Recipe/EditorButtonRow"; | import EditorButtonRow from "@/components/Recipe/EditorButtonRow"; | ||||||
| import { user } from "@/mixins/user"; | import { user } from "@/mixins/user"; | ||||||
|  | import store from "@/store"; | ||||||
|  | import { router } from "@/routes"; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
| @@ -166,8 +168,12 @@ export default { | |||||||
|         return api.recipes.recipeImage(image) + "&rnd=" + this.imageKey; |         return api.recipes.recipeImage(image) + "&rnd=" + this.imageKey; | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     deleteRecipe() { |     async deleteRecipe() { | ||||||
|       api.recipes.delete(this.recipeDetails.slug); |       let response = await api.recipes.delete(this.recipeDetails.slug); | ||||||
|  |       if (response) { | ||||||
|  |         store.dispatch("requestRecentRecipes"); | ||||||
|  |         router.push(`/`); | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     validateRecipe() { |     validateRecipe() { | ||||||
|       if (this.jsonEditor) { |       if (this.jsonEditor) { | ||||||
| @@ -176,18 +182,19 @@ export default { | |||||||
|         return this.$refs.recipeEditor.validateRecipe(); |         return this.$refs.recipeEditor.validateRecipe(); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     async saveImage() { |     async saveImage(overrideSuccessMsg = false) { | ||||||
|       if (this.fileObject) { |       if (this.fileObject) { | ||||||
|         await api.recipes.updateImage(this.recipeDetails.slug, this.fileObject); |         if (api.recipes.updateImage(this.recipeDetails.slug, this.fileObject, overrideSuccessMsg)) { | ||||||
|  |           this.imageKey += 1; | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|       this.imageKey += 1; |  | ||||||
|     }, |     }, | ||||||
|     async saveRecipe() { |     async saveRecipe() { | ||||||
|       if (this.validateRecipe()) { |       if (this.validateRecipe()) { | ||||||
|         let slug = await api.recipes.update(this.recipeDetails); |         let slug = await api.recipes.update(this.recipeDetails); | ||||||
|  |  | ||||||
|         if (this.fileObject) { |         if (this.fileObject) { | ||||||
|           this.saveImage(); |           this.saveImage(true); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.form = false; |         this.form = false; | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ | |||||||
|       <v-row dense class="mt-0 flex-row align-center justify-space-around"> |       <v-row dense class="mt-0 flex-row align-center justify-space-around"> | ||||||
|         <v-col> |         <v-col> | ||||||
|           <h3 class="pl-2 text-center headline"> |           <h3 class="pl-2 text-center headline"> | ||||||
|             {{ $t("search.category-filter") }} |             {{ $t("category.category-filter") }} | ||||||
|           </h3> |           </h3> | ||||||
|           <FilterSelector class="mb-1" @update="updateCatParams" /> |           <FilterSelector class="mb-1" @update="updateCatParams" /> | ||||||
|           <CategoryTagSelector |           <CategoryTagSelector | ||||||
|   | |||||||
| @@ -2,6 +2,9 @@ import Planner from "@/pages/MealPlan/Planner"; | |||||||
| import ThisWeek from "@/pages/MealPlan/ThisWeek"; | import ThisWeek from "@/pages/MealPlan/ThisWeek"; | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
|  |  | ||||||
|  | import i18n from '@/i18n.js'; | ||||||
|  | import utils from "@/utils"; | ||||||
|  |  | ||||||
| export const mealRoutes = [ | export const mealRoutes = [ | ||||||
|   { |   { | ||||||
|     path: "/meal-plan/planner", |     path: "/meal-plan/planner", | ||||||
| @@ -21,7 +24,12 @@ export const mealRoutes = [ | |||||||
|     path: "/meal-plan/today", |     path: "/meal-plan/today", | ||||||
|     beforeEnter: async (_to, _from, next) => { |     beforeEnter: async (_to, _from, next) => { | ||||||
|       await todaysMealRoute().then(redirect => { |       await todaysMealRoute().then(redirect => { | ||||||
|         next(redirect); |         if(redirect) { | ||||||
|  |           next(redirect); | ||||||
|  |         } else { | ||||||
|  |           utils.notify.error(i18n.t('meal-plan.no-meal-planned-for-today')); | ||||||
|  |           next(_from); | ||||||
|  |         } | ||||||
|       }); |       }); | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| @@ -29,5 +37,9 @@ export const mealRoutes = [ | |||||||
|  |  | ||||||
| async function todaysMealRoute() { | async function todaysMealRoute() { | ||||||
|   const response = await api.mealPlans.today(); |   const response = await api.mealPlans.today(); | ||||||
|   return "/recipe/" + response.data; |   if (response.status == 200 && response.data) { | ||||||
|  |     return "/recipe/" + response.data; | ||||||
|  |   } else { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,13 +2,12 @@ import operator | |||||||
| import shutil | import shutil | ||||||
| from typing import Optional | from typing import Optional | ||||||
|  |  | ||||||
| from fastapi import APIRouter, Depends, File, HTTPException, UploadFile | from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status | ||||||
| from mealie.core.config import app_dirs | from mealie.core.config import app_dirs | ||||||
| from mealie.core.security import create_file_token | from mealie.core.security import create_file_token | ||||||
| from mealie.db.db_setup import generate_session | from mealie.db.db_setup import generate_session | ||||||
| from mealie.routes.deps import get_current_user, validate_file_token | from mealie.routes.deps import get_current_user, validate_file_token | ||||||
| from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup | from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from mealie.services.backups import imports | from mealie.services.backups import imports | ||||||
| from mealie.services.backups.exports import backup_all | from mealie.services.backups.exports import backup_all | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
| @@ -31,30 +30,27 @@ def available_imports(): | |||||||
|     return Imports(imports=imports, templates=templates) |     return Imports(imports=imports, templates=templates) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.post("/export/database", status_code=201) | @router.post("/export/database", status_code=status.HTTP_201_CREATED) | ||||||
| def export_database(data: BackupJob, session: Session = Depends(generate_session)): | def export_database(data: BackupJob, session: Session = Depends(generate_session)): | ||||||
|     """Generates a backup of the recipe database in json format.""" |     """Generates a backup of the recipe database in json format.""" | ||||||
|     export_path = backup_all( |  | ||||||
|         session=session, |  | ||||||
|         tag=data.tag, |  | ||||||
|         templates=data.templates, |  | ||||||
|         export_recipes=data.options.recipes, |  | ||||||
|         export_settings=data.options.settings, |  | ||||||
|         export_pages=data.options.pages, |  | ||||||
|         export_themes=data.options.themes, |  | ||||||
|         export_users=data.options.users, |  | ||||||
|         export_groups=data.options.groups, |  | ||||||
|     ) |  | ||||||
|     try: |     try: | ||||||
|         return SnackResponse.success("Backup Created at " + export_path) |         export_path = backup_all( | ||||||
|     except: |             session=session, | ||||||
|         HTTPException( |             tag=data.tag, | ||||||
|             status_code=400, |             templates=data.templates, | ||||||
|             detail=SnackResponse.error("Error Creating Backup. See Log File"), |             export_recipes=data.options.recipes, | ||||||
|  |             export_settings=data.options.settings, | ||||||
|  |             export_pages=data.options.pages, | ||||||
|  |             export_themes=data.options.themes, | ||||||
|  |             export_users=data.options.users, | ||||||
|  |             export_groups=data.options.groups, | ||||||
|         ) |         ) | ||||||
|  |         return {"export_path": export_path} | ||||||
|  |     except: | ||||||
|  |         raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.post("/upload") | @router.post("/upload", status_code=status.HTTP_200_OK) | ||||||
| def upload_backup_file(archive: UploadFile = File(...)): | def upload_backup_file(archive: UploadFile = File(...)): | ||||||
|     """ Upload a .zip File to later be imported into Mealie """ |     """ Upload a .zip File to later be imported into Mealie """ | ||||||
|     dest = app_dirs.BACKUP_DIR.joinpath(archive.filename) |     dest = app_dirs.BACKUP_DIR.joinpath(archive.filename) | ||||||
| @@ -62,10 +58,9 @@ def upload_backup_file(archive: UploadFile = File(...)): | |||||||
|     with dest.open("wb") as buffer: |     with dest.open("wb") as buffer: | ||||||
|         shutil.copyfileobj(archive.file, buffer) |         shutil.copyfileobj(archive.file, buffer) | ||||||
|  |  | ||||||
|     if dest.is_file: |     if not dest.is_file: | ||||||
|         return SnackResponse.success("Backup uploaded") |         raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
|     else: |  | ||||||
|         return SnackResponse.error("Failure uploading file") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.get("/{file_name}/download") | @router.get("/{file_name}/download") | ||||||
| @@ -76,7 +71,7 @@ async def download_backup_file(file_name: str): | |||||||
|     return {"fileToken": create_file_token(file)} |     return {"fileToken": create_file_token(file)} | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.post("/{file_name}/import", status_code=200) | @router.post("/{file_name}/import", status_code=status.HTTP_200_OK) | ||||||
| def import_database(file_name: str, import_data: ImportJob, session: Session = Depends(generate_session)): | def import_database(file_name: str, import_data: ImportJob, session: Session = Depends(generate_session)): | ||||||
|     """ Import a database backup file generated from Mealie. """ |     """ Import a database backup file generated from Mealie. """ | ||||||
|  |  | ||||||
| @@ -94,16 +89,14 @@ def import_database(file_name: str, import_data: ImportJob, session: Session = D | |||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.delete("/{file_name}/delete", status_code=200) | @router.delete("/{file_name}/delete", status_code=status.HTTP_200_OK) | ||||||
| def delete_backup(file_name: str): | def delete_backup(file_name: str): | ||||||
|     """ Removes a database backup from the file system """ |     """ Removes a database backup from the file system """ | ||||||
|  |     file_path = app_dirs.BACKUP_DIR.joinpath(file_name) | ||||||
|  |  | ||||||
|  |     if not file_path.is_file(): | ||||||
|  |         raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
|     try: |     try: | ||||||
|         app_dirs.BACKUP_DIR.joinpath(file_name).unlink() |         file_path.unlink() | ||||||
|     except: |     except: | ||||||
|         HTTPException( |         raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR ) | ||||||
|             status_code=400, |  | ||||||
|             detail=SnackResponse.error("Unable to Delete Backup. See Log File"), |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     return SnackResponse.error(f"{file_name} Deleted") |  | ||||||
|   | |||||||
| @@ -1,8 +1,7 @@ | |||||||
| from fastapi import APIRouter, Depends | from fastapi import APIRouter, Depends, status, HTTPException | ||||||
| from mealie.db.database import db | from mealie.db.database import db | ||||||
| from mealie.db.db_setup import generate_session | from mealie.db.db_setup import generate_session | ||||||
| from mealie.routes.deps import get_current_user | from mealie.routes.deps import get_current_user | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from mealie.schema.user import GroupBase, GroupInDB, UpdateGroup, UserInDB | from mealie.schema.user import GroupBase, GroupInDB, UpdateGroup, UserInDB | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
|  |  | ||||||
| @@ -30,7 +29,7 @@ async def get_current_user_group( | |||||||
|     return db.groups.get(session, current_user.group, "name") |     return db.groups.get(session, current_user.group, "name") | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.post("") | @router.post("", status_code=status.HTTP_201_CREATED) | ||||||
| async def create_group( | async def create_group( | ||||||
|     group_data: GroupBase, |     group_data: GroupBase, | ||||||
|     current_user=Depends(get_current_user), |     current_user=Depends(get_current_user), | ||||||
| @@ -40,9 +39,8 @@ async def create_group( | |||||||
|  |  | ||||||
|     try: |     try: | ||||||
|         db.groups.create(session, group_data.dict()) |         db.groups.create(session, group_data.dict()) | ||||||
|         return SnackResponse.success("User Group Created", {"created": True}) |  | ||||||
|     except: |     except: | ||||||
|         return SnackResponse.error("User Group Creation Failed") |         raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.put("/{id}") | @router.put("/{id}") | ||||||
| @@ -55,8 +53,6 @@ async def update_group_data( | |||||||
|     """ Updates a User Group """ |     """ Updates a User Group """ | ||||||
|     db.groups.update(session, id, group_data.dict()) |     db.groups.update(session, id, group_data.dict()) | ||||||
|  |  | ||||||
|     return SnackResponse.success("Group Settings Updated") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.delete("/{id}") | @router.delete("/{id}") | ||||||
| async def delete_user_group( | async def delete_user_group( | ||||||
| @@ -65,16 +61,23 @@ async def delete_user_group( | |||||||
|     """ Removes a user group from the database """ |     """ Removes a user group from the database """ | ||||||
|  |  | ||||||
|     if id == 1: |     if id == 1: | ||||||
|         return SnackResponse.error("Cannot delete default group") |         raise HTTPException( | ||||||
|  |             status_code=status.HTTP_400_BAD_REQUEST, | ||||||
|  |             detail='DEFAULT_GROUP' | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     group: GroupInDB = db.groups.get(session, id) |     group: GroupInDB = db.groups.get(session, id) | ||||||
|  |  | ||||||
|     if not group: |     if not group: | ||||||
|         return SnackResponse.error("Group not found") |         raise HTTPException( | ||||||
|  |             status_code=status.HTTP_400_BAD_REQUEST, | ||||||
|  |             detail='GROUP_NOT_FOUND' | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     if not group.users == []: |     if not group.users == []: | ||||||
|         return SnackResponse.error("Cannot delete group with users") |         raise HTTPException( | ||||||
|  |             status_code=status.HTTP_400_BAD_REQUEST, | ||||||
|  |             detail='GROUP_WITH_USERS' | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     db.groups.delete(session, id) |     db.groups.delete(session, id) | ||||||
|  |  | ||||||
|     return |  | ||||||
|   | |||||||
| @@ -1,9 +1,8 @@ | |||||||
| from fastapi import APIRouter, Depends, HTTPException | from fastapi import APIRouter, Depends, HTTPException, status | ||||||
| from mealie.db.database import db | from mealie.db.database import db | ||||||
| from mealie.db.db_setup import generate_session | from mealie.db.db_setup import generate_session | ||||||
| from mealie.routes.deps import get_current_user | from mealie.routes.deps import get_current_user | ||||||
| from mealie.schema.meal import MealPlanIn, MealPlanInDB | from mealie.schema.meal import MealPlanIn, MealPlanInDB | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from mealie.schema.user import GroupInDB, UserInDB | from mealie.schema.user import GroupInDB, UserInDB | ||||||
| from mealie.services.image import image | from mealie.services.image import image | ||||||
| from mealie.services.meal_services import get_todays_meal, process_meals | from mealie.services.meal_services import get_todays_meal, process_meals | ||||||
| @@ -23,15 +22,13 @@ def get_all_meals( | |||||||
|     return db.groups.get_meals(session, current_user.group) |     return db.groups.get_meals(session, current_user.group) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.post("/create") | @router.post("/create", status_code=status.HTTP_201_CREATED) | ||||||
| def create_meal_plan( | def create_meal_plan( | ||||||
|     data: MealPlanIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user) |     data: MealPlanIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user) | ||||||
| ): | ): | ||||||
|     """ Creates a meal plan database entry """ |     """ Creates a meal plan database entry """ | ||||||
|     processed_plan = process_meals(session, data) |     processed_plan = process_meals(session, data) | ||||||
|     db.meals.create(session, processed_plan.dict()) |     return db.meals.create(session, processed_plan.dict()) | ||||||
|  |  | ||||||
|     return SnackResponse.success("Mealplan Created") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.put("/{plan_id}") | @router.put("/{plan_id}") | ||||||
| @@ -44,25 +41,28 @@ def update_meal_plan( | |||||||
|     """ Updates a meal plan based off ID """ |     """ Updates a meal plan based off ID """ | ||||||
|     processed_plan = process_meals(session, meal_plan) |     processed_plan = process_meals(session, meal_plan) | ||||||
|     processed_plan = MealPlanInDB(uid=plan_id, **processed_plan.dict()) |     processed_plan = MealPlanInDB(uid=plan_id, **processed_plan.dict()) | ||||||
|     db.meals.update(session, plan_id, processed_plan.dict()) |     try: | ||||||
|  |         db.meals.update(session, plan_id, processed_plan.dict()) | ||||||
|     return SnackResponse.info("Mealplan Updated") |     except: | ||||||
|  |         raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.delete("/{plan_id}") | @router.delete("/{plan_id}") | ||||||
| def delete_meal_plan(plan_id, session: Session = Depends(generate_session), current_user=Depends(get_current_user)): | def delete_meal_plan(plan_id, session: Session = Depends(generate_session), current_user=Depends(get_current_user)): | ||||||
|     """ Removes a meal plan from the database """ |     """ Removes a meal plan from the database """ | ||||||
|  |  | ||||||
|     db.meals.delete(session, plan_id) |     try: | ||||||
|  |         db.meals.delete(session, plan_id) | ||||||
|     return SnackResponse.error("Mealplan Deleted") |     except: | ||||||
|  |         raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.get("/this-week", response_model=MealPlanInDB) | @router.get("/this-week", response_model=MealPlanInDB) | ||||||
| def get_this_week(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)): | def get_this_week(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)): | ||||||
|     """ Returns the meal plan data for this week """ |     """ Returns the meal plan data for this week """ | ||||||
|  |     plans = db.groups.get_meals(session, current_user.group) | ||||||
|     return db.groups.get_meals(session, current_user.group)[0] |     if plans: | ||||||
|  |         return plans[0] | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.get("/today", tags=["Meal Plan"]) | @router.get("/today", tags=["Meal Plan"]) | ||||||
| @@ -74,8 +74,8 @@ def get_today(session: Session = Depends(generate_session), current_user: UserIn | |||||||
|  |  | ||||||
|     group_in_db: GroupInDB = db.groups.get(session, current_user.group, "name") |     group_in_db: GroupInDB = db.groups.get(session, current_user.group, "name") | ||||||
|     recipe = get_todays_meal(session, group_in_db) |     recipe = get_todays_meal(session, group_in_db) | ||||||
|  |     if recipe: | ||||||
|     return recipe.slug |         return recipe.slug | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.get("/today/image", tags=["Meal Plan"]) | @router.get("/today/image", tags=["Meal Plan"]) | ||||||
| @@ -90,8 +90,8 @@ def get_todays_image(session: Session = Depends(generate_session), group_name: s | |||||||
|     if recipe: |     if recipe: | ||||||
|         recipe_image = image.read_image(recipe.slug, image_type=image.IMG_OPTIONS.ORIGINAL_IMAGE) |         recipe_image = image.read_image(recipe.slug, image_type=image.IMG_OPTIONS.ORIGINAL_IMAGE) | ||||||
|     else: |     else: | ||||||
|         raise HTTPException(404, "no meal for today") |         raise HTTPException( status.HTTP_404_NOT_FOUND ) | ||||||
|     if recipe_image: |     if recipe_image: | ||||||
|         return FileResponse(recipe_image) |         return FileResponse(recipe_image) | ||||||
|     else: |     else: | ||||||
|         raise HTTPException(404, "file not found") |         raise HTTPException( status.HTTP_404_NOT_FOUND ) | ||||||
|   | |||||||
| @@ -2,12 +2,11 @@ import operator | |||||||
| import shutil | import shutil | ||||||
| from typing import List | from typing import List | ||||||
|  |  | ||||||
| from fastapi import APIRouter, Depends, File, UploadFile | from fastapi import APIRouter, Depends, File, UploadFile, status | ||||||
| from mealie.core.config import app_dirs | from mealie.core.config import app_dirs | ||||||
| from mealie.db.db_setup import generate_session | from mealie.db.db_setup import generate_session | ||||||
| from mealie.routes.deps import get_current_user | from mealie.routes.deps import get_current_user | ||||||
| from mealie.schema.migration import MigrationFile, Migrations | from mealie.schema.migration import MigrationFile, Migrations | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from mealie.services.migrations import migration | from mealie.services.migrations import migration | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
|  |  | ||||||
| @@ -42,7 +41,7 @@ def import_migration(import_type: migration.Migration, file_name: str, session: | |||||||
|     return migration.migrate(import_type, file_path, session) |     return migration.migrate(import_type, file_path, session) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.delete("/{import_type}/{file_name}/delete") | @router.delete("/{import_type}/{file_name}/delete", status_code=status.HTTP_200_OK) | ||||||
| def delete_migration_data(import_type: migration.Migration, file_name: str): | def delete_migration_data(import_type: migration.Migration, file_name: str): | ||||||
|     """ Removes migration data from the file system """ |     """ Removes migration data from the file system """ | ||||||
|  |  | ||||||
| @@ -53,12 +52,11 @@ def delete_migration_data(import_type: migration.Migration, file_name: str): | |||||||
|     elif remove_path.is_dir(): |     elif remove_path.is_dir(): | ||||||
|         shutil.rmtree(remove_path) |         shutil.rmtree(remove_path) | ||||||
|     else: |     else: | ||||||
|         SnackResponse.error("File/Folder not found.") |         raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
|  |  | ||||||
|     return SnackResponse.error(f"Migration Data Remove: {remove_path.absolute()}") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.post("/{import_type}/upload") |  | ||||||
|  | @router.post("/{import_type}/upload", status_code=status.HTTP_200_OK) | ||||||
| def upload_nextcloud_zipfile(import_type: migration.Migration, archive: UploadFile = File(...)): | def upload_nextcloud_zipfile(import_type: migration.Migration, archive: UploadFile = File(...)): | ||||||
|     """ Upload a .zip File to later be imported into Mealie """ |     """ Upload a .zip File to later be imported into Mealie """ | ||||||
|     dir = app_dirs.MIGRATION_DIR.joinpath(import_type.value) |     dir = app_dirs.MIGRATION_DIR.joinpath(import_type.value) | ||||||
| @@ -68,7 +66,5 @@ def upload_nextcloud_zipfile(import_type: migration.Migration, archive: UploadFi | |||||||
|     with dest.open("wb") as buffer: |     with dest.open("wb") as buffer: | ||||||
|         shutil.copyfileobj(archive.file, buffer) |         shutil.copyfileobj(archive.file, buffer) | ||||||
|  |  | ||||||
|     if dest.is_file: |     if not dest.is_file: | ||||||
|         return SnackResponse.success("Migration data uploaded") |         raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
|     else: |  | ||||||
|         return SnackResponse.error("Failure uploading file") |  | ||||||
| @@ -1,9 +1,8 @@ | |||||||
| from fastapi import APIRouter, Depends | from fastapi import APIRouter, Depends, HTTPException, status | ||||||
| from mealie.db.database import db | from mealie.db.database import db | ||||||
| from mealie.db.db_setup import generate_session | from mealie.db.db_setup import generate_session | ||||||
| from mealie.routes.deps import get_current_user | from mealie.routes.deps import get_current_user | ||||||
| from mealie.schema.category import CategoryIn, RecipeCategoryResponse | from mealie.schema.category import CategoryIn, RecipeCategoryResponse | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
|  |  | ||||||
| router = APIRouter( | router = APIRouter( | ||||||
| @@ -36,7 +35,10 @@ async def create_recipe_category( | |||||||
| ): | ): | ||||||
|     """ Creates a Category in the database """ |     """ Creates a Category in the database """ | ||||||
|  |  | ||||||
|     return db.categories.create(session, category.dict()) |     try: | ||||||
|  |         return db.categories.create(session, category.dict()) | ||||||
|  |     except: | ||||||
|  |         raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.put("/{category}", response_model=RecipeCategoryResponse) | @router.put("/{category}", response_model=RecipeCategoryResponse) | ||||||
| @@ -48,7 +50,10 @@ async def update_recipe_category( | |||||||
| ): | ): | ||||||
|     """ Updates an existing Tag in the database """ |     """ Updates an existing Tag in the database """ | ||||||
|  |  | ||||||
|     return db.categories.update(session, category, new_category.dict()) |     try: | ||||||
|  |         return db.categories.update(session, category, new_category.dict()) | ||||||
|  |     except: | ||||||
|  |         raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.delete("/{category}") | @router.delete("/{category}") | ||||||
| @@ -59,6 +64,7 @@ async def delete_recipe_category( | |||||||
|     category does not impact a recipe. The category will be removed |     category does not impact a recipe. The category will be removed | ||||||
|     from any recipes that contain it""" |     from any recipes that contain it""" | ||||||
|  |  | ||||||
|     db.categories.delete(session, category) |     try: | ||||||
|  |         db.categories.delete(session, category) | ||||||
|     return SnackResponse.error(f"Category Deleted: {category}") |     except: | ||||||
|  |         raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
|   | |||||||
| @@ -1,13 +1,12 @@ | |||||||
| import shutil | import shutil | ||||||
|  |  | ||||||
| from fastapi import APIRouter, Depends, File, Form | from fastapi import APIRouter, Depends, File, Form, status, HTTPException | ||||||
| from fastapi.datastructures import UploadFile | from fastapi.datastructures import UploadFile | ||||||
| from mealie.core.config import app_dirs | from mealie.core.config import app_dirs | ||||||
| from mealie.db.database import db | from mealie.db.database import db | ||||||
| from mealie.db.db_setup import generate_session | from mealie.db.db_setup import generate_session | ||||||
| from mealie.routes.deps import get_current_user | from mealie.routes.deps import get_current_user | ||||||
| from mealie.schema.recipe import Recipe, RecipeAsset | from mealie.schema.recipe import Recipe, RecipeAsset | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from slugify import slugify | from slugify import slugify | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
| from starlette.responses import FileResponse | from starlette.responses import FileResponse | ||||||
| @@ -41,10 +40,10 @@ def upload_recipe_asset( | |||||||
|     with dest.open("wb") as buffer: |     with dest.open("wb") as buffer: | ||||||
|         shutil.copyfileobj(file.file, buffer) |         shutil.copyfileobj(file.file, buffer) | ||||||
|  |  | ||||||
|     if dest.is_file(): |     if not dest.is_file(): | ||||||
|         recipe: Recipe = db.recipes.get(session, recipe_slug) |         raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR ) | ||||||
|         recipe.assets.append(asset_in) |  | ||||||
|         db.recipes.update(session, recipe_slug, recipe.dict()) |     recipe: Recipe = db.recipes.get(session, recipe_slug) | ||||||
|         return asset_in |     recipe.assets.append(asset_in) | ||||||
|     else: |     db.recipes.update(session, recipe_slug, recipe.dict()) | ||||||
|         return SnackResponse.error("Failure uploading file") |     return asset_in | ||||||
| @@ -1,12 +1,11 @@ | |||||||
| from enum import Enum | from enum import Enum | ||||||
|  |  | ||||||
| from fastapi import APIRouter, Depends, File, Form, HTTPException | from fastapi import APIRouter, Depends, File, Form, HTTPException, status | ||||||
| from fastapi.responses import FileResponse | from fastapi.responses import FileResponse | ||||||
| from mealie.db.database import db | from mealie.db.database import db | ||||||
| from mealie.db.db_setup import generate_session | from mealie.db.db_setup import generate_session | ||||||
| from mealie.routes.deps import get_current_user | from mealie.routes.deps import get_current_user | ||||||
| from mealie.schema.recipe import Recipe, RecipeURLIn | from mealie.schema.recipe import Recipe, RecipeURLIn | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from mealie.services.image.image import IMG_OPTIONS, delete_image, read_image, rename_image, scrape_image, write_image | from mealie.services.image.image import IMG_OPTIONS, delete_image, read_image, rename_image, scrape_image, write_image | ||||||
| from mealie.services.scraper.scraper import create_from_url | from mealie.services.scraper.scraper import create_from_url | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
| @@ -100,9 +99,8 @@ def delete_recipe( | |||||||
|         db.recipes.delete(session, recipe_slug) |         db.recipes.delete(session, recipe_slug) | ||||||
|         delete_image(recipe_slug) |         delete_image(recipe_slug) | ||||||
|     except: |     except: | ||||||
|         raise HTTPException(status_code=404, detail=SnackResponse.error("Unable to Delete Recipe")) |         raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
|  |  | ||||||
|     return SnackResponse.error(f"Recipe {recipe_slug} Deleted") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ImageType(str, Enum): | class ImageType(str, Enum): | ||||||
| @@ -125,7 +123,7 @@ async def get_recipe_img(recipe_slug: str, image_type: ImageType = ImageType.ori | |||||||
|     if recipe_image: |     if recipe_image: | ||||||
|         return FileResponse(recipe_image) |         return FileResponse(recipe_image) | ||||||
|     else: |     else: | ||||||
|         raise HTTPException(404, "file not found") |         raise HTTPException( status.HTTP_404_NOT_FOUND ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.put("/{recipe_slug}/image") | @router.put("/{recipe_slug}/image") | ||||||
| @@ -152,5 +150,3 @@ def scrape_image_url( | |||||||
|     """ Removes an existing image and replaces it with the incoming file. """ |     """ Removes an existing image and replaces it with the incoming file. """ | ||||||
|  |  | ||||||
|     scrape_image(url.url, recipe_slug) |     scrape_image(url.url, recipe_slug) | ||||||
|  |  | ||||||
|     return SnackResponse.success("Recipe Image Updated") |  | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ from mealie.db.database import db | |||||||
| from mealie.db.db_setup import generate_session | from mealie.db.db_setup import generate_session | ||||||
| from mealie.routes.deps import get_current_user | from mealie.routes.deps import get_current_user | ||||||
| from mealie.schema.category import RecipeTagResponse, TagIn | from mealie.schema.category import RecipeTagResponse, TagIn | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
|  |  | ||||||
| router = APIRouter(tags=["Recipes"]) | router = APIRouter(tags=["Recipes"]) | ||||||
| @@ -58,6 +57,7 @@ async def delete_recipe_tag( | |||||||
|     tag does not impact a recipe. The tag will be removed |     tag does not impact a recipe. The tag will be removed | ||||||
|     from any recipes that contain it""" |     from any recipes that contain it""" | ||||||
|  |  | ||||||
|     db.tags.delete(session, tag) |     try: | ||||||
|  |         db.tags.delete(session, tag) | ||||||
|     return SnackResponse.error(f"Tag Deleted: {tag}") |     except: | ||||||
|  |         raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ from mealie.db.database import db | |||||||
| from mealie.db.db_setup import generate_session | from mealie.db.db_setup import generate_session | ||||||
| from mealie.routes.deps import get_current_user | from mealie.routes.deps import get_current_user | ||||||
| from mealie.schema.settings import CustomPageBase, CustomPageOut | from mealie.schema.settings import CustomPageBase, CustomPageOut | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from mealie.schema.user import UserInDB | from mealie.schema.user import UserInDB | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
|  |  | ||||||
| @@ -29,8 +28,6 @@ async def create_new_page( | |||||||
|  |  | ||||||
|     db.custom_pages.create(session, new_page.dict()) |     db.custom_pages.create(session, new_page.dict()) | ||||||
|  |  | ||||||
|     return SnackResponse.success("New Page Created") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.put("") | @router.put("") | ||||||
| async def update_multiple_pages( | async def update_multiple_pages( | ||||||
| @@ -41,7 +38,6 @@ async def update_multiple_pages( | |||||||
|     """ Update multiple custom pages """ |     """ Update multiple custom pages """ | ||||||
|     for page in pages: |     for page in pages: | ||||||
|         db.custom_pages.update(session, page.id, page.dict()) |         db.custom_pages.update(session, page.id, page.dict()) | ||||||
|     return SnackResponse.success("Pages Updated") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.get("/{id}") | @router.get("/{id}") | ||||||
| @@ -57,7 +53,7 @@ async def get_single_page( | |||||||
|  |  | ||||||
|  |  | ||||||
| @router.put("/{id}") | @router.put("/{id}") | ||||||
| async def update_single_age( | async def update_single_page( | ||||||
|     data: CustomPageOut, |     data: CustomPageOut, | ||||||
|     id: int, |     id: int, | ||||||
|     session: Session = Depends(generate_session), |     session: Session = Depends(generate_session), | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ from mealie.db.database import db | |||||||
| from mealie.db.db_setup import generate_session | from mealie.db.db_setup import generate_session | ||||||
| from mealie.routes.deps import get_current_user | from mealie.routes.deps import get_current_user | ||||||
| from mealie.schema.settings import SiteSettings | from mealie.schema.settings import SiteSettings | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from mealie.schema.user import GroupInDB, UserInDB | from mealie.schema.user import GroupInDB, UserInDB | ||||||
| from mealie.utils.post_webhooks import post_webhooks | from mealie.utils.post_webhooks import post_webhooks | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
| @@ -27,8 +26,6 @@ def update_settings( | |||||||
|     """ Returns Site Settings """ |     """ Returns Site Settings """ | ||||||
|     db.settings.update(session, 1, data.dict()) |     db.settings.update(session, 1, data.dict()) | ||||||
|  |  | ||||||
|     return SnackResponse.success("Settings Updated") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.post("/webhooks/test") | @router.post("/webhooks/test") | ||||||
| def test_webhooks( | def test_webhooks( | ||||||
|   | |||||||
| @@ -1,8 +1,7 @@ | |||||||
| from fastapi import APIRouter, Depends | from fastapi import APIRouter, Depends, status, HTTPException | ||||||
| from mealie.db.database import db | from mealie.db.database import db | ||||||
| from mealie.db.db_setup import generate_session | from mealie.db.db_setup import generate_session | ||||||
| from mealie.routes.deps import get_current_user | from mealie.routes.deps import get_current_user | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from mealie.schema.theme import SiteTheme | from mealie.schema.theme import SiteTheme | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
|  |  | ||||||
| @@ -16,12 +15,11 @@ def get_all_themes(session: Session = Depends(generate_session)): | |||||||
|     return db.themes.get_all(session) |     return db.themes.get_all(session) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.post("/themes/create") | @router.post("/themes/create", status_code=status.HTTP_201_CREATED) | ||||||
| def create_theme(data: SiteTheme, session: Session = Depends(generate_session), current_user=Depends(get_current_user)): | def create_theme(data: SiteTheme, session: Session = Depends(generate_session), current_user=Depends(get_current_user)): | ||||||
|     """ Creates a site color theme database entry """ |     """ Creates a site color theme database entry """ | ||||||
|     db.themes.create(session, data.dict()) |     db.themes.create(session, data.dict()) | ||||||
|  |  | ||||||
|     return SnackResponse.success("Theme Saved") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.get("/themes/{theme_name}") | @router.get("/themes/{theme_name}") | ||||||
| @@ -30,7 +28,7 @@ def get_single_theme(theme_name: str, session: Session = Depends(generate_sessio | |||||||
|     return db.themes.get(session, theme_name) |     return db.themes.get(session, theme_name) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.put("/themes/{theme_name}") | @router.put("/themes/{theme_name}", status_code=status.HTTP_200_OK) | ||||||
| def update_theme( | def update_theme( | ||||||
|     theme_name: str, |     theme_name: str, | ||||||
|     data: SiteTheme, |     data: SiteTheme, | ||||||
| @@ -40,12 +38,11 @@ def update_theme( | |||||||
|     """ Update a theme database entry """ |     """ Update a theme database entry """ | ||||||
|     db.themes.update(session, theme_name, data.dict()) |     db.themes.update(session, theme_name, data.dict()) | ||||||
|  |  | ||||||
|     return SnackResponse.info(f"Theme Updated: {theme_name}") |  | ||||||
|  |  | ||||||
|  | @router.delete("/themes/{theme_name}", status_code=status.HTTP_200_OK) | ||||||
| @router.delete("/themes/{theme_name}") |  | ||||||
| def delete_theme(theme_name: str, session: Session = Depends(generate_session), current_user=Depends(get_current_user)): | def delete_theme(theme_name: str, session: Session = Depends(generate_session), current_user=Depends(get_current_user)): | ||||||
|     """ Deletes theme from the database """ |     """ Deletes theme from the database """ | ||||||
|     db.themes.delete(session, theme_name) |     try: | ||||||
|  |         db.themes.delete(session, theme_name) | ||||||
|     return SnackResponse.error(f"Theme Deleted: {theme_name}") |     except: | ||||||
|  |         raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ from mealie.core import security | |||||||
| from mealie.core.security import authenticate_user | from mealie.core.security import authenticate_user | ||||||
| from mealie.db.db_setup import generate_session | from mealie.db.db_setup import generate_session | ||||||
| from mealie.routes.deps import get_current_user | from mealie.routes.deps import get_current_user | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from mealie.schema.user import UserInDB | from mealie.schema.user import UserInDB | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
|  |  | ||||||
| @@ -28,15 +27,11 @@ def get_token( | |||||||
|     if not user: |     if not user: | ||||||
|         raise HTTPException( |         raise HTTPException( | ||||||
|             status_code=status.HTTP_401_UNAUTHORIZED, |             status_code=status.HTTP_401_UNAUTHORIZED, | ||||||
|             detail="Incorrect username or password", |  | ||||||
|             headers={"WWW-Authenticate": "Bearer"}, |             headers={"WWW-Authenticate": "Bearer"}, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     access_token = security.create_access_token(dict(sub=email)) |     access_token = security.create_access_token(dict(sub=email)) | ||||||
|     return SnackResponse.success( |     return {"access_token": access_token, "token_type": "bearer"} | ||||||
|         "User Successfully Logged In", |  | ||||||
|         {"access_token": access_token, "token_type": "bearer"}, |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.get("/refresh") | @router.get("/refresh") | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import shutil | import shutil | ||||||
| from datetime import timedelta | from datetime import timedelta | ||||||
|  |  | ||||||
| from fastapi import APIRouter, Depends, File, UploadFile | from fastapi import APIRouter, Depends, File, UploadFile, status, HTTPException | ||||||
| from fastapi.responses import FileResponse | from fastapi.responses import FileResponse | ||||||
| from mealie.core import security | from mealie.core import security | ||||||
| from mealie.core.config import app_dirs, settings | from mealie.core.config import app_dirs, settings | ||||||
| @@ -9,7 +9,6 @@ from mealie.core.security import get_password_hash, verify_password | |||||||
| from mealie.db.database import db | from mealie.db.database import db | ||||||
| from mealie.db.db_setup import generate_session | from mealie.db.db_setup import generate_session | ||||||
| from mealie.routes.deps import get_current_user | from mealie.routes.deps import get_current_user | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from mealie.schema.user import ChangePassword, UserBase, UserIn, UserInDB, UserOut | from mealie.schema.user import ChangePassword, UserBase, UserIn, UserInDB, UserOut | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
|  |  | ||||||
| @@ -25,8 +24,7 @@ async def create_user( | |||||||
|  |  | ||||||
|     new_user.password = get_password_hash(new_user.password) |     new_user.password = get_password_hash(new_user.password) | ||||||
|  |  | ||||||
|     data = db.users.create(session, new_user.dict()) |     return db.users.create(session, new_user.dict()) | ||||||
|     return SnackResponse.success(f"User Created: {new_user.full_name}", data) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.get("", response_model=list[UserOut]) | @router.get("", response_model=list[UserOut]) | ||||||
| @@ -35,10 +33,10 @@ async def get_all_users( | |||||||
|     session: Session = Depends(generate_session), |     session: Session = Depends(generate_session), | ||||||
| ): | ): | ||||||
|  |  | ||||||
|     if current_user.admin: |     if not current_user.admin: | ||||||
|         return db.users.get_all(session) |         raise HTTPException( status.HTTP_403_FORBIDDEN ) | ||||||
|     else: |      | ||||||
|         return {"details": "user not authorized"} |     return db.users.get_all(session) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.get("/self", response_model=UserOut) | @router.get("/self", response_model=UserOut) | ||||||
| @@ -68,7 +66,6 @@ async def reset_user_password( | |||||||
|     new_password = get_password_hash(settings.DEFAULT_PASSWORD) |     new_password = get_password_hash(settings.DEFAULT_PASSWORD) | ||||||
|     db.users.update_password(session, id, new_password) |     db.users.update_password(session, id, new_password) | ||||||
|  |  | ||||||
|     return SnackResponse.success("Users Password Reset") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.put("/{id}") | @router.put("/{id}") | ||||||
| @@ -85,8 +82,7 @@ async def update_user( | |||||||
|     if current_user.id == id: |     if current_user.id == id: | ||||||
|         access_token = security.create_access_token(data=dict(sub=new_data.email)) |         access_token = security.create_access_token(data=dict(sub=new_data.email)) | ||||||
|         token = {"access_token": access_token, "token_type": "bearer"} |         token = {"access_token": access_token, "token_type": "bearer"} | ||||||
|  |         return token | ||||||
|     return SnackResponse.success("User Updated", token) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.get("/{id}/image") | @router.get("/{id}/image") | ||||||
| @@ -121,10 +117,8 @@ async def update_user_image( | |||||||
|     with dest.open("wb") as buffer: |     with dest.open("wb") as buffer: | ||||||
|         shutil.copyfileobj(profile_image.file, buffer) |         shutil.copyfileobj(profile_image.file, buffer) | ||||||
|  |  | ||||||
|     if dest.is_file: |     if not dest.is_file: | ||||||
|         return SnackResponse.success("File uploaded") |         raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR ) | ||||||
|     else: |  | ||||||
|         return SnackResponse.error("Failure uploading file") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.put("/{id}/password") | @router.put("/{id}/password") | ||||||
| @@ -139,12 +133,12 @@ async def update_password( | |||||||
|     match_passwords = verify_password(password_change.current_password, current_user.password) |     match_passwords = verify_password(password_change.current_password, current_user.password) | ||||||
|     match_id = current_user.id == id |     match_id = current_user.id == id | ||||||
|  |  | ||||||
|     if match_passwords and match_id: |     if not ( match_passwords and match_id ): | ||||||
|         new_password = get_password_hash(password_change.new_password) |         raise HTTPException( status.HTTP_401_UNAUTHORIZED ) | ||||||
|         db.users.update_password(session, id, new_password) |  | ||||||
|         return SnackResponse.success("Password Updated") |     new_password = get_password_hash(password_change.new_password) | ||||||
|     else: |     db.users.update_password(session, id, new_password) | ||||||
|         return SnackResponse.error("Existing password does not match") |          | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.delete("/{id}") | @router.delete("/{id}") | ||||||
| @@ -156,8 +150,13 @@ async def delete_user( | |||||||
|     """ Removes a user from the database. Must be the current user or a super user""" |     """ Removes a user from the database. Must be the current user or a super user""" | ||||||
|  |  | ||||||
|     if id == 1: |     if id == 1: | ||||||
|         return SnackResponse.error("Error! Cannot Delete Super User") |         raise HTTPException( | ||||||
|  |             status_code=status.HTTP_403_FORBIDDEN, | ||||||
|  |             detail='SUPER_USER' | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     if current_user.id == id or current_user.admin: |     if current_user.id == id or current_user.admin: | ||||||
|         db.users.delete(session, id) |         try: | ||||||
|         return SnackResponse.error("User Deleted") |             db.users.delete(session, id) | ||||||
|  |         except: | ||||||
|  |             raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
| @@ -6,7 +6,6 @@ from mealie.db.db_setup import generate_session | |||||||
| from fastapi import APIRouter, Depends | from fastapi import APIRouter, Depends | ||||||
| from mealie.routes.deps import get_current_user | from mealie.routes.deps import get_current_user | ||||||
| from mealie.schema.sign_up import SignUpIn, SignUpOut, SignUpToken | from mealie.schema.sign_up import SignUpIn, SignUpOut, SignUpToken | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from mealie.schema.user import UserIn, UserInDB | from mealie.schema.user import UserIn, UserInDB | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
|  |  | ||||||
| @@ -33,18 +32,16 @@ async def create_user_sign_up_key( | |||||||
| ): | ): | ||||||
|     """ Generates a Random Token that a new user can sign up with """ |     """ Generates a Random Token that a new user can sign up with """ | ||||||
|  |  | ||||||
|     if current_user.admin: |     if not current_user.admin: | ||||||
|         sign_up = { |         raise HTTPException( status.HTTP_403_FORBIDDEN ) | ||||||
|             "token": str(uuid.uuid1().hex), |  | ||||||
|             "name": key_data.name, |  | ||||||
|             "admin": key_data.admin, |  | ||||||
|         } |  | ||||||
|         db_entry = db.sign_ups.create(session, sign_up) |  | ||||||
|  |  | ||||||
|         return db_entry |     sign_up = { | ||||||
|  |         "token": str(uuid.uuid1().hex), | ||||||
|  |         "name": key_data.name, | ||||||
|  |         "admin": key_data.admin, | ||||||
|  |     } | ||||||
|  |     return db.sign_ups.create(session, sign_up) | ||||||
|  |  | ||||||
|     else: |  | ||||||
|         return {"details": "not authorized"} |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.post("/{token}") | @router.post("/{token}") | ||||||
| @@ -58,7 +55,7 @@ async def create_user_with_token( | |||||||
|     # Validate Token |     # Validate Token | ||||||
|     db_entry: SignUpOut = db.sign_ups.get(session, token, limit=1) |     db_entry: SignUpOut = db.sign_ups.get(session, token, limit=1) | ||||||
|     if not db_entry: |     if not db_entry: | ||||||
|         return SnackResponse.error("Invalid Token") |         raise HTTPException( status.HTTP_401_UNAUTHORIZED ) | ||||||
|  |  | ||||||
|     # Create User |     # Create User | ||||||
|     new_user.admin = db_entry.admin |     new_user.admin = db_entry.admin | ||||||
| @@ -68,9 +65,6 @@ async def create_user_with_token( | |||||||
|     # DeleteToken |     # DeleteToken | ||||||
|     db.sign_ups.delete(session, token) |     db.sign_ups.delete(session, token) | ||||||
|  |  | ||||||
|     # Respond |  | ||||||
|     return SnackResponse.success(f"User Created: {new_user.full_name}", data) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.delete("/{token}") | @router.delete("/{token}") | ||||||
| async def delete_token( | async def delete_token( | ||||||
| @@ -79,8 +73,7 @@ async def delete_token( | |||||||
|     session: Session = Depends(generate_session), |     session: Session = Depends(generate_session), | ||||||
| ): | ): | ||||||
|     """ Removed a token from the database """ |     """ Removed a token from the database """ | ||||||
|     if current_user.admin: |     if not current_user.admin: | ||||||
|         db.sign_ups.delete(session, token) |         raise HTTPException( status.HTTP_403_FORBIDDEN ) | ||||||
|         return SnackResponse.error("Sign Up Token Deleted") |      | ||||||
|     else: |     db.sign_ups.delete(session, token) | ||||||
|         return {"details", "not authorized"} |  | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ from typing import Optional | |||||||
|  |  | ||||||
| from fastapi import APIRouter, Depends | from fastapi import APIRouter, Depends | ||||||
| from mealie.routes.deps import validate_file_token | from mealie.routes.deps import validate_file_token | ||||||
| from mealie.schema.snackbar import SnackResponse |  | ||||||
| from starlette.responses import FileResponse | from starlette.responses import FileResponse | ||||||
|  |  | ||||||
| router = APIRouter(prefix="/api/utils", tags=["Utils"], include_in_schema=True) | router = APIRouter(prefix="/api/utils", tags=["Utils"], include_in_schema=True) | ||||||
| @@ -14,7 +13,7 @@ async def download_file(file_path: Optional[Path] = Depends(validate_file_token) | |||||||
|     """ Uses a file token obtained by an active user to retrieve a file from the operating |     """ Uses a file token obtained by an active user to retrieve a file from the operating | ||||||
|     system. """ |     system. """ | ||||||
|     print("File Name:", file_path) |     print("File Name:", file_path) | ||||||
|     if file_path.is_file(): |     if not file_path.is_file(): | ||||||
|         return FileResponse(file_path, media_type="application/octet-stream", filename=file_path.name) |         raise HTTPException( status.HTTP_400_BAD_REQUEST ) | ||||||
|     else: |  | ||||||
|         return SnackResponse.error("No File Found") |     return FileResponse(file_path, media_type="application/octet-stream", filename=file_path.name) | ||||||
|   | |||||||
| @@ -1,26 +0,0 @@ | |||||||
| class SnackResponse: |  | ||||||
|     @staticmethod |  | ||||||
|     def _create_response(message: str, type: str, additional_data: dict = None) -> dict: |  | ||||||
|  |  | ||||||
|         snackbar = {"snackbar": {"text": message, "type": type}} |  | ||||||
|  |  | ||||||
|         if additional_data: |  | ||||||
|             snackbar.update(additional_data) |  | ||||||
|  |  | ||||||
|         return snackbar |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def success(message: str, additional_data: dict = None) -> dict: |  | ||||||
|         return SnackResponse._create_response(message, "success", additional_data) |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def info(message: str, additional_data: dict = None) -> dict: |  | ||||||
|         return SnackResponse._create_response(message, "info", additional_data) |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def warning(message: str, additional_data: dict = None) -> dict: |  | ||||||
|         return SnackResponse._create_response(message, "warning", additional_data) |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def error(message: str, additional_data: dict = None) -> dict: |  | ||||||
|         return SnackResponse._create_response(message, "error", additional_data) |  | ||||||
| @@ -13,12 +13,7 @@ def group_data(): | |||||||
| def test_create_group(api_client: TestClient, api_routes: AppRoutes, token): | def test_create_group(api_client: TestClient, api_routes: AppRoutes, token): | ||||||
|     response = api_client.post(api_routes.groups, json={"name": "Test Group"}, headers=token) |     response = api_client.post(api_routes.groups, json={"name": "Test Group"}, headers=token) | ||||||
|  |  | ||||||
|     assert response.status_code == 200 |     assert response.status_code == 201 | ||||||
|  |  | ||||||
|     assert json.loads(response.content) == { |  | ||||||
|         "snackbar": {"text": "User Group Created", "type": "success"}, |  | ||||||
|         "created": True, |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_self_group(api_client: TestClient, api_routes: AppRoutes, token): | def test_get_self_group(api_client: TestClient, api_routes: AppRoutes, token): | ||||||
| @@ -42,7 +37,6 @@ def test_update_group(api_client: TestClient, api_routes: AppRoutes, token): | |||||||
|     # Test Update |     # Test Update | ||||||
|     response = api_client.put(api_routes.groups_id(2), json=new_data, headers=token) |     response = api_client.put(api_routes.groups_id(2), json=new_data, headers=token) | ||||||
|     assert response.status_code == 200 |     assert response.status_code == 200 | ||||||
|     assert json.loads(response.text) == {"snackbar": {"text": "Group Settings Updated", "type": "success"}} |  | ||||||
|      |      | ||||||
|     # Validate Changes |     # Validate Changes | ||||||
|     response = api_client.get(api_routes.groups, headers=token) |     response = api_client.get(api_routes.groups, headers=token) | ||||||
| @@ -51,13 +45,13 @@ def test_update_group(api_client: TestClient, api_routes: AppRoutes, token): | |||||||
|     assert next(id_2) == new_data |     assert next(id_2) == new_data | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_block_delete(api_client: TestClient, api_routes: AppRoutes, token): | def test_home_group_not_deletable(api_client: TestClient, api_routes: AppRoutes, token): | ||||||
|     response = api_client.delete(api_routes.groups_id(1), headers=token) |     response = api_client.delete(api_routes.groups_id(1), headers=token) | ||||||
|  |  | ||||||
|     assert json.loads(response.text) == {"snackbar": {"text": "Cannot delete default group", "type": "error"}} |     assert response.status_code == 400 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_delete_group(api_client: TestClient, api_routes: AppRoutes, token): | def test_delete_group(api_client: TestClient, api_routes: AppRoutes, token): | ||||||
|     response = api_client.delete(api_routes.groups_id(2), headers=token) |     response = api_client.delete(api_routes.groups_id(2), headers=token) | ||||||
|  |  | ||||||
|     assert json.loads(response.text) is None |     assert response.status_code == 200 | ||||||
|   | |||||||
| @@ -50,7 +50,7 @@ def test_create_mealplan(api_client: TestClient, api_routes: AppRoutes, slug_1, | |||||||
|     meal_plan = get_meal_plan_template(slug_1, slug_2) |     meal_plan = get_meal_plan_template(slug_1, slug_2) | ||||||
|  |  | ||||||
|     response = api_client.post(api_routes.meal_plans_create, json=meal_plan, headers=token) |     response = api_client.post(api_routes.meal_plans_create, json=meal_plan, headers=token) | ||||||
|     assert response.status_code == 200 |     assert response.status_code == 201 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_read_mealplan(api_client: TestClient, api_routes: AppRoutes, slug_1, slug_2, token): | def test_read_mealplan(api_client: TestClient, api_routes: AppRoutes, slug_1, slug_2, token): | ||||||
|   | |||||||
| @@ -62,7 +62,7 @@ def test_default_theme(api_client: TestClient, api_routes: AppRoutes, default_th | |||||||
| def test_create_theme(api_client: TestClient, api_routes: AppRoutes, new_theme, token): | def test_create_theme(api_client: TestClient, api_routes: AppRoutes, new_theme, token): | ||||||
|  |  | ||||||
|     response = api_client.post(api_routes.themes_create, json=new_theme, headers=token) |     response = api_client.post(api_routes.themes_create, json=new_theme, headers=token) | ||||||
|     assert response.status_code == 200 |     assert response.status_code == 201 | ||||||
|  |  | ||||||
|     response = api_client.get(api_routes.themes_theme_name(new_theme.get("name")), headers=token) |     response = api_client.get(api_routes.themes_theme_name(new_theme.get("name")), headers=token) | ||||||
|     assert response.status_code == 200 |     assert response.status_code == 200 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user