Move API texts to frontend + better status codes

This commit is contained in:
Florian Dupret 2021-04-27 20:03:54 +02:00
commit 330941522f
50 changed files with 490 additions and 321 deletions

View file

@ -1,64 +1,45 @@
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";
axios.defaults.headers.common[ axios.defaults.headers.common[
"Authorization" "Authorization"
] = `Bearer ${store.getters.getToken}`; ] = `Bearer ${store.getters.getToken}`;
function processResponse(response) {
try {
utils.notify.show(response.data.snackbar.text, response.data.snackbar.type);
} catch (err) {
return;
}
return;
}
const apiReq = { const apiReq = {
post: async function(url, data) { post: async function(url, data) {
let response = await axios.post(url, data).catch(function(error) { let response = await axios.post(url, data).catch(function(error) {
if (error.response) { if (error.response) {
processResponse(error.response);
return error.response; return error.response;
} }
}); });
processResponse(response);
return response; return response;
}, },
put: async function(url, data) { put: async function(url, data) {
let response = await axios.put(url, data).catch(function(error) { let response = await axios.put(url, data).catch(function(error) {
if (error.response) { if (error.response) {
processResponse(error.response); return error.response;
return response;
} else return; } else return;
}); });
processResponse(response);
return response; return response;
}, },
get: async function(url, data) { get: async function(url, data) {
let response = await axios.get(url, data).catch(function(error) { let response = await axios.get(url, data).catch(function(error) {
if (error.response) { if (error.response) {
processResponse(error.response); return error.response;
return response;
} else return; } else return;
}); });
processResponse(response);
return response; return response;
}, },
delete: async function(url, data) { delete: async function(url, data) {
let response = await axios.delete(url, data).catch(function(error) { let response = await axios.delete(url, data).catch(function(error) {
if (error.response) { if (error.response) {
processResponse(error.response); return error.response;
return response;
} }
}); });
processResponse(response);
return response; return response;
}, },

View file

@ -40,7 +40,7 @@ export const backupAPI = {
* @param {string} fileName * @param {string} fileName
*/ */
async delete(fileName) { async delete(fileName) {
await apiReq.delete(backupURLs.deleteBackup(fileName)); return await apiReq.delete(backupURLs.deleteBackup(fileName));
}, },
/** /**
* Creates a backup on the serve given a set of options * Creates a backup on the serve given a set of options

View file

@ -27,7 +27,7 @@ export const categoryAPI = {
async delete(category) { async delete(category) {
let response = await apiReq.delete(categoryURLs.deleteCategory(category)); let response = await apiReq.delete(categoryURLs.deleteCategory(category));
store.dispatch("requestCategories"); store.dispatch("requestCategories");
return response.data; return response;
}, },
}; };

View file

@ -17,11 +17,11 @@ export const groupAPI = {
}, },
async create(name) { async create(name) {
let response = await apiReq.post(groupsURLs.create, { name: name }); let response = await apiReq.post(groupsURLs.create, { name: name });
return response.data; return response;
}, },
async delete(id) { async delete(id) {
let response = await apiReq.delete(groupsURLs.delete(id)); let response = await apiReq.delete(groupsURLs.delete(id));
return response.data; return response;
}, },
async current() { async current() {
let response = await apiReq.get(groupsURLs.current); let response = await apiReq.get(groupsURLs.current);
@ -29,6 +29,6 @@ export const groupAPI = {
}, },
async update(data) { async update(data) {
let response = await apiReq.put(groupsURLs.update(data.id), data); let response = await apiReq.put(groupsURLs.update(data.id), data);
return response.data; return response;
}, },
}; };

View file

@ -18,7 +18,7 @@ export const migrationAPI = {
}, },
async delete(folder, file) { async delete(folder, file) {
let response = await apiReq.delete(migrationURLs.delete(folder, file)); let response = await apiReq.delete(migrationURLs.delete(folder, file));
return response.data; 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));

View file

@ -1,7 +1,6 @@
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";
const prefix = baseURL + "recipes/"; const prefix = baseURL + "recipes/";
@ -72,9 +71,7 @@ export const recipeAPI = {
}, },
async delete(recipeSlug) { async delete(recipeSlug) {
await apiReq.delete(recipeURLs.delete(recipeSlug)); return await apiReq.delete(recipeURLs.delete(recipeSlug));
store.dispatch("requestRecentRecipes");
router.push(`/`);
}, },
async allSummary(start = 0, limit = 9999) { async allSummary(start = 0, limit = 9999) {

View file

@ -20,11 +20,9 @@ export const signupAPI = {
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;
}, },
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;
}, },
}; };

View file

@ -21,7 +21,7 @@ export const siteSettingsAPI = {
async update(body) { async update(body) {
let response = await apiReq.put(settingsURLs.updateSiteSettings, body); let response = await apiReq.put(settingsURLs.updateSiteSettings, body);
store.dispatch("requestSiteSettings"); store.dispatch("requestSiteSettings");
return response.data; return response;
}, },
async getPages() { async getPages() {
@ -35,18 +35,15 @@ export const siteSettingsAPI = {
}, },
async createPage(body) { async createPage(body) {
let response = await apiReq.post(settingsURLs.customPages, body); return await apiReq.post(settingsURLs.customPages, body);
return response.data;
}, },
async deletePage(id) { async deletePage(id) {
let response = await apiReq.delete(settingsURLs.customPage(id)); return await apiReq.delete(settingsURLs.customPage(id));
return response.data;
}, },
async updatePage(body) { async updatePage(body) {
let response = await apiReq.put(settingsURLs.customPage(body.id), body); return await apiReq.put(settingsURLs.customPage(body.id), body);
return response.data;
}, },
async updateAllPages(allPages) { async updateAllPages(allPages) {

View file

@ -23,8 +23,7 @@ export const themeAPI = {
}, },
async create(postBody) { async create(postBody) {
let response = await apiReq.post(settingsURLs.createTheme, postBody); return await apiReq.post(settingsURLs.createTheme, postBody);
return response.data;
}, },
async update(themeName, colors) { async update(themeName, colors) {
@ -32,12 +31,10 @@ export const themeAPI = {
name: themeName, name: themeName,
colors: colors, colors: colors,
}; };
let response = await apiReq.put(settingsURLs.updateTheme(themeName), body); return await apiReq.put(settingsURLs.updateTheme(themeName), body);
return response.data;
}, },
async delete(themeName) { async delete(themeName) {
let response = await apiReq.delete(settingsURLs.deleteTheme(themeName)); return await apiReq.delete(settingsURLs.deleteTheme(themeName));
return response.data;
}, },
}; };

View file

@ -10,6 +10,6 @@ export const utilsAPI = {
"Content-Type": "multipart/form-data", "Content-Type": "multipart/form-data",
}, },
}); });
return response.data; return response;
}, },
}; };

View file

@ -37,8 +37,7 @@ export const userAPI = {
return response.data; return response.data;
}, },
async create(user) { async create(user) {
let response = await apiReq.post(usersURLs.users, user); return await apiReq.post(usersURLs.users, user);
return response.data;
}, },
async self() { async self() {
let response = await apiReq.get(usersURLs.self); let response = await apiReq.get(usersURLs.self);
@ -49,19 +48,16 @@ export const userAPI = {
return response.data; return response.data;
}, },
async update(user) { async update(user) {
let response = await apiReq.put(usersURLs.userID(user.id), user); return await apiReq.put(usersURLs.userID(user.id), user);
return response.data;
}, },
async changePassword(id, password) { async changePassword(id, password) {
let response = await apiReq.put(usersURLs.password(id), password); let response = await apiReq.put(usersURLs.password(id), password);
return response.data; return response.data;
}, },
async delete(id) { async delete(id) {
let response = await apiReq.delete(usersURLs.userID(id)); return await apiReq.delete(usersURLs.userID(id));
return response.data;
}, },
async resetPassword(id) { async resetPassword(id) {
let response = await apiReq.put(usersURLs.resetPassword(id)); return await apiReq.put(usersURLs.resetPassword(id));
return response.data;
}, },
}; };

View file

@ -61,6 +61,7 @@
<script> <script>
import { api } from "@/api"; import { api } from "@/api";
import utils from "@/utils";
export default { export default {
props: {}, props: {},
data() { data() {
@ -98,9 +99,11 @@ export default {
this.error = true; this.error = true;
} }
if (key.status != 200) { if (key.status != 200) {
utils.notify.error(this.$t('user.incorrect-username-or-password'));
this.error = true; this.error = true;
this.loading = false; this.loading = false;
} else { } else {
utils.notify.success(this.$t('user.user-successfully-logged-in'));
this.clear(); this.clear();
this.$store.commit("setToken", key.data.access_token); this.$store.commit("setToken", key.data.access_token);
this.$emit("logged-in"); this.$emit("logged-in");

View file

@ -85,6 +85,7 @@
<script> <script>
import { api } from "@/api"; import { api } from "@/api";
import { validators } from "@/mixins/validators"; import { validators } from "@/mixins/validators";
import utils from "@/utils";
export default { export default {
mixins: [validators], mixins: [validators],
data() { data() {
@ -132,18 +133,20 @@ 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); let response = await api.signUps.createUser(this.token, userData);
successUser = response.snackbar.text.includes("Created"); if (response.status != 200) {
utils.notify.error(this.$t('user.you-are-not-allowed-to-create-a-user'));
} else {
utils.notify.success(this.$t('user.user-created'));
this.$emit("user-created");
this.$router.push("/");
}
} }
this.$emit("user-created");
this.loading = false; this.loading = false;
if (successUser) {
this.$router.push("/");
}
}, },
}, },
}; };

View file

@ -36,8 +36,13 @@ export default {
return utils.getDateAsPythonDate(dateObject); return utils.getDateAsPythonDate(dateObject);
}, },
async update() { async update() {
await api.mealPlans.update(this.mealPlan.uid, this.mealPlan); const response = await api.mealPlans.update(this.mealPlan.uid, this.mealPlan);
this.$emit("updated"); if (response.status != 200) {
utils.notify.error(this.$t('meal-plan.mealplan-update-failed'));
} else {
utils.notify.success(this.$t('meal-plan.mealplan-updated'));
this.$emit("updated");
}
}, },
}, },
}; };

View file

@ -196,7 +196,12 @@ export default {
endDate: this.endDate, endDate: this.endDate,
meals: this.meals, meals: this.meals,
}; };
await api.mealPlans.create(mealBody); const response = await api.mealPlans.create(mealBody);
if (response.status != 201) {
utils.notify.error(this.$t('meal-plan.mealplan-creation-failed'));
} else {
utils.notify.success(this.$t('meal-plan.mealplan-created'));
}
this.$emit(CREATE_EVENT); this.$emit(CREATE_EVENT);
this.meals = []; this.meals = [];
this.startDate = null; this.startDate = null;

View file

@ -3,7 +3,7 @@
<v-menu offset-y top nudge-top="6" :close-on-content-click="false"> <v-menu offset-y top nudge-top="6" :close-on-content-click="false">
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn color="accent" dark v-bind="attrs" v-on="on"> <v-btn color="accent" dark v-bind="attrs" v-on="on">
{{$t('recipe.image')}} {{$t('general.image')}}
</v-btn> </v-btn>
</template> </template>
<v-card width="400"> <v-card width="400">
@ -46,6 +46,7 @@ const REFRESH_EVENT = "refresh";
const UPLOAD_EVENT = "upload"; const UPLOAD_EVENT = "upload";
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn"; import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
import { api } from "@/api"; import { api } from "@/api";
import utils from "@/utils";
export default { export default {
components: { components: {
TheUploadBtn, TheUploadBtn,
@ -64,6 +65,11 @@ export default {
async getImageFromURL() { async getImageFromURL() {
this.loading = true; this.loading = true;
const response = await api.recipes.updateImagebyURL(this.slug, this.url); const response = await api.recipes.updateImagebyURL(this.slug, this.url);
if (response.status != 200) {
utils.notify.error(this.$t('general.image-upload-failed'));
} else {
utils.notify.success(this.$t('general.image-updated'));
}
if (response) this.$emit(REFRESH_EVENT); if (response) this.$emit(REFRESH_EVENT);
this.loading = false; this.loading = false;
}, },

View file

@ -16,6 +16,7 @@
<script> <script>
const UPLOAD_EVENT = "uploaded"; const UPLOAD_EVENT = "uploaded";
import { api } from "@/api"; import { api } from "@/api";
import utils from "@/utils";
export default { export default {
props: { props: {
post: { post: {
@ -55,8 +56,13 @@ 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); const response = await api.utils.uploadFile(this.url, formData);
if(response.status != 200) {
utils.notify.error(this.$t('general.failure-uploading-file'));
} else {
utils.notify.success(this.$t('general.file-uploaded'));
}
this.isSelecting = false; this.isSelecting = false;
this.$emit(UPLOAD_EVENT); this.$emit(UPLOAD_EVENT);
} }

View file

@ -34,15 +34,22 @@
"enabled": "Enabled", "enabled": "Enabled",
"exception": "Exception", "exception": "Exception",
"failed-count": "Failed: {count}", "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-updated": "Image updated",
"image-upload-failed": "Image upload failed",
"import": "Import", "import": "Import",
"monday": "Monday", "monday": "Monday",
"name": "Name", "name": "Name",
"no": "No", "no": "No",
"not-authorized": "Not authorized",
"ok": "OK", "ok": "OK",
"options": "Options:", "options": "Options:",
"random": "Random", "random": "Random",
@ -79,6 +86,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",
@ -90,6 +105,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"
@ -108,6 +124,12 @@
"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-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": {
@ -122,7 +144,6 @@
"fat-content": "Fat Content", "fat-content": "Fat Content",
"fiber-content": "Fiber Content", "fiber-content": "Fiber Content",
"grams": "grams", "grams": "grams",
"image": "Image",
"ingredient": "Ingredient", "ingredient": "Ingredient",
"ingredients": "Ingredients", "ingredients": "Ingredients",
"instructions": "Instructions", "instructions": "Instructions",
@ -139,7 +160,9 @@
"perform-time": "Cook Time", "perform-time": "Cook Time",
"prep-time": "Prep Time", "prep-time": "Prep Time",
"protein-content": "Protein Content", "protein-content": "Protein Content",
"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",
"servings": "Servings", "servings": "Servings",
"sodium-content": "Sodium Content", "sodium-content": "Sodium Content",
@ -148,6 +171,7 @@
"tags": "Tags", "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": {
@ -166,19 +190,26 @@
"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",
"import-summary": "Import Summary", "import-summary": "Import Summary",
"partial-backup": "Partial Backup" "partial-backup": "Partial Backup",
"unable-to-delete-backup-see-log-file": "Unable to Delete Backup. See Log File"
}, },
"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.",
"category-deleted": "Category Deleted",
"category-deletion-failed": "Category deletion failed",
"change-password": "Change Password", "change-password": "Change Password",
"current": "Version:", "current": "Version:",
"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",
@ -198,6 +229,8 @@
"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",
@ -208,6 +241,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",
@ -215,9 +251,12 @@
"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"
}, },
"webhooks": { "webhooks": {
@ -227,11 +266,14 @@
"webhook-url": "Webhook URL" "webhook-url": "Webhook URL"
} }
}, },
"this": {},
"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-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/>?",
"cannot-delete-default-group": "Cannot delete default group",
"cannot-delete-group-with-users": "Cannot delete group with users",
"confirm-group-deletion": "Confirm Group Deletion", "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",
@ -244,12 +286,19 @@
"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",
"error-updating-group": "Error updating group",
"existing-password-does-not-match": "Existing password does not match",
"full-name": "Full Name", "full-name": "Full Name",
"group": "Group", "group": "Group",
"group-deleted": "Group deleted",
"group-deletion-failed": "Group deletion failed",
"group-id-with-value": "Group ID: {groupID}", "group-id-with-value": "Group ID: {groupID}",
"group-name": "Group Name", "group-name": "Group Name",
"group-not-found": "Group not found",
"groups": "Groups", "groups": "Groups",
"groups-can-only-be-set-by-administrators": "Groups can only be set by administrators", "groups-can-only-be-set-by-administrators": "Groups can only be set by administrators",
"incorrect-username-or-password": "Incorrect username or password",
"link-id": "Link ID", "link-id": "Link ID",
"link-name": "Link Name", "link-name": "Link Name",
"login": "Login", "login": "Login",
@ -257,20 +306,34 @@
"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", "sign-up-links": "Sign Up Links",
"sign-up-token-deleted": "Sign Up Token Deleted",
"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-created": "User created",
"user-creation-failed": "User creation failed",
"user-deleted": "User deleted",
"user-group": "User Group", "user-group": "User Group",
"user-group-created": "User Group Created",
"user-group-creation-failed": "User Group Creation Failed",
"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"
} }
} }

View file

@ -40,6 +40,7 @@
<script> <script>
import ImportDialog from "./ImportDialog"; import ImportDialog from "./ImportDialog";
import { api } from "@/api"; import { api } from "@/api";
import utils from "@/utils";
export default { export default {
props: { props: {
backups: Array, backups: Array,
@ -68,10 +69,15 @@ export default {
this.$emit("finished", importData); this.$emit("finished", importData);
}, },
deleteBackup(data) { async deleteBackup(data) {
this.$emit("loading"); this.$emit("loading");
api.backups.delete(data.name); const response = await api.backups.delete(data.name);
if (response.status != 200) {
utils.notify.error(this.$t('settings.backup.unable-to-delete-backup-see-log-file'));
} else {
utils.notify.success(this.$t('settings.backup.backup-deleted'));
}
this.selectedBackup = null; this.selectedBackup = null;
this.backupLoading = false; this.backupLoading = false;

View file

@ -44,6 +44,7 @@
<script> <script>
import ImportOptions from "./ImportOptions"; import ImportOptions from "./ImportOptions";
import { api } from "@/api"; import { api } from "@/api";
import utils from "@/utils";
export default { export default {
components: { ImportOptions }, components: { ImportOptions },
data() { data() {
@ -97,10 +98,15 @@ export default {
templates: this.selectedTemplates, templates: this.selectedTemplates,
}; };
await api.backups.create(data); const response = await api.backups.create(data);
this.loading = false; this.loading = false;
if (response.status != 201) {
utils.notify.error(this.$t('settings.backup.error-creating-backup-see-log-file'));
} else {
utils.notify.success(this.$t('settings.backup.backup-created-at-response-export_path', {path: response.data.export_path}));
this.$emit("created");
}
this.$emit("created");
}, },
appendTemplate(templateName) { appendTemplate(templateName) {
if (this.selectedTemplates.includes(templateName)) { if (this.selectedTemplates.includes(templateName)) {

View file

@ -57,6 +57,7 @@
const RENDER_EVENT = "update"; const RENDER_EVENT = "update";
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog"; import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
import { api } from "@/api"; import { api } from "@/api";
import utils from "@/utils";
export default { export default {
components: { ConfirmationDialog }, components: { ConfirmationDialog },
props: { props: {
@ -91,8 +92,28 @@ export default {
this.$refs.deleteGroupConfirm.open(); this.$refs.deleteGroupConfirm.open();
}, },
async deleteGroup() { async deleteGroup() {
await api.groups.delete(this.group.id); const response = await api.groups.delete(this.group.id);
this.$emit(RENDER_EVENT); if (response.status != 200) {
switch(response.data.detail) {
case 'GROUP_WITH_USERS':
utils.notify.error(this.$t('user.cannot-delete-group-with-users'));
break;
case 'GROUP_NOT_FOUND':
utils.notify.error(this.$t('user.group-not-found'));
break;
case 'DEFAULT_GROUP':
utils.notify.error(this.$t('user.cannot-delete-default-group'));
break;
default:
utils.notify.error(this.$t('user.group-deletion-failed'));
}
} else {
utils.notify.success(this.$t('user.group-deleted'));
this.$emit(RENDER_EVENT);
}
}, },
closeGroupDelete() { closeGroupDelete() {
console.log("Close Delete"); console.log("Close Delete");

View file

@ -86,6 +86,7 @@
import { validators } from "@/mixins/validators"; import { validators } from "@/mixins/validators";
import { api } from "@/api"; import { api } from "@/api";
import GroupCard from "./GroupCard"; import GroupCard from "./GroupCard";
import utils from "@/utils";
export default { export default {
components: { GroupCard }, components: { GroupCard },
mixins: [validators], mixins: [validators],
@ -104,8 +105,11 @@ export default {
methods: { methods: {
async createGroup() { async createGroup() {
this.groupLoading = true; this.groupLoading = true;
let response = await api.groups.create(this.newGroupName); const response = await api.groups.create(this.newGroupName);
if (response.created) { if (response.status != 201) {
utils.notify.error(this.$t('user.user-group-creation-failed'));
} else {
utils.notify.success(this.$t('user.user-group-created'));
this.groupLoading = false; this.groupLoading = false;
this.groupDialog = false; this.groupDialog = false;
this.$store.dispatch("requestAllGroups"); this.$store.dispatch("requestAllGroups");

View file

@ -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"
/> />
@ -109,6 +109,7 @@
<script> <script>
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog"; import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
import { api } from "@/api"; import { api } from "@/api";
import utils from "@/utils";
import { validators } from "@/mixins/validators"; import { validators } from "@/mixins/validators";
export default { export default {
components: { ConfirmationDialog }, components: { ConfirmationDialog },
@ -181,8 +182,13 @@ export default {
this.links = await api.signUps.getAll(); this.links = await api.signUps.getAll();
}, },
async deleteUser() { async deleteToken() {
await api.signUps.deleteToken(this.activeId); const response = await api.signUps.deleteToken(this.activeId);
if (response.status != 200) {
utils.notify.error(this.$t('general.not-authorized'));
} else {
utils.notify.success(this.$t('user.sign-up-token-deleted'));
}
this.initialize(); this.initialize();
}, },
@ -197,7 +203,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() {

View file

@ -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">
@ -146,6 +146,7 @@
<script> <script>
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog"; import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
import { api } from "@/api"; import { api } from "@/api";
import utils from "@/utils";
import { validators } from "@/mixins/validators"; import { validators } from "@/mixins/validators";
export default { export default {
components: { ConfirmationDialog }, components: { ConfirmationDialog },
@ -223,8 +224,20 @@ export default {
}, },
async deleteUser() { async deleteUser() {
await api.users.delete(this.activeId); const response = await api.users.delete(this.activeId);
this.initialize(); if (response.status != 200) {
switch(response.data.detail) {
case 'SUPER_USER':
utils.notify.error(this.$t('user.error-cannot-delete-super-user'));
break;
default:
utils.notify.error(this.$t('user.you-are-not-allowed-to-delete-this-user'));
}
} else {
utils.notify.success(this.$t('user.user-deleted'));
this.initialize();
}
}, },
editItem(item) { editItem(item) {
@ -264,17 +277,40 @@ 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() { async resetPassword() {
api.users.resetPassword(this.editedItem.id); const response = await api.users.resetPassword(this.editedItem.id);
if (response.status != 200) {
utils.notify.error(this.$t('user.password-reset-failed'));
} else {
utils.notify.success(this.$t('user.password-has-been-reset-to-the-default-password'));
}
}, },
async createUser() {
const response = await api.users.create(this.editedItem);
if(response.status!=201) {
utils.notify.error(this.$t('user.user-creation-failed'));
} else {
utils.notify.success(this.$t('user.user-created'));
this.close();
}
},
async updateUser() {
const response = await api.users.update(this.editedItem);
if(response.status!=200) {
utils.notify.error(this.$t('user.user-update-failed'));
} else {
utils.notify.success(this.$t('user.user-updated'));
this.close();
}
}
}, },
}; };
</script> </script>

View file

@ -84,6 +84,7 @@
import { api } from "@/api"; import { api } from "@/api";
import TimePickerDialog from "@/components/FormHelpers/TimePickerDialog"; import TimePickerDialog from "@/components/FormHelpers/TimePickerDialog";
import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector"; import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector";
import utils from "@/utils";
export default { export default {
components: { components: {
TimePickerDialog, TimePickerDialog,
@ -135,9 +136,14 @@ export default {
this.groupSettings.webhookUrls.splice(index, 1); this.groupSettings.webhookUrls.splice(index, 1);
}, },
async saveGroupSettings() { async saveGroupSettings() {
await api.groups.update(this.groupSettings); const response = await api.groups.update(this.groupSettings);
await this.$store.dispatch("requestCurrentGroup"); if (response.status != 200) {
this.getSiteSettings(); utils.notify.error(this.$t('user.error-updating-group'));
} else {
utils.notify.success(this.$t('settings.group-settings-updated'));
await this.$store.dispatch("requestCurrentGroup");
this.getSiteSettings();
}
}, },
testWebhooks() { testWebhooks() {
api.settings.testWebhooks(); api.settings.testWebhooks();

View file

@ -69,6 +69,7 @@
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn"; import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
import { api } from "@/api"; import { api } from "@/api";
import MigrationDialog from "./MigrationDialog"; import MigrationDialog from "./MigrationDialog";
import utils from "@/utils";
export default { export default {
props: { props: {
folder: String, folder: String,
@ -86,9 +87,14 @@ export default {
}; };
}, },
methods: { methods: {
deleteMigration(file_name) { async deleteMigration(file_name) {
api.migrations.delete(this.folder, file_name); const response = await api.migrations.delete(this.folder, file_name);
this.$emit("refresh"); if (response.status != 200) {
utils.notify.error(this.$t('general.file-folder-not-found'));
} else {
utils.notify.success(this.$t('migration.migration-data-removed'));
this.$emit("refresh");
}
}, },
async importMigration(file_name) { async importMigration(file_name) {
this.loading = true; this.loading = true;

View file

@ -147,6 +147,7 @@
// import AvatarPicker from '@/components/AvatarPicker' // import AvatarPicker from '@/components/AvatarPicker'
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn"; import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
import { api } from "@/api"; import { api } from "@/api";
import utils from "@/utils";
import { validators } from "@/mixins/validators"; import { validators } from "@/mixins/validators";
import { initials } from "@/mixins/initials"; import { initials } from "@/mixins/initials";
export default { export default {
@ -201,11 +202,16 @@ 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.status != 200) {
this.refreshProfile(); utils.notify.error(this.$t('user.user-update-failed'));
this.loading = false; } else {
this.$store.dispatch("requestUserData"); utils.notify.success(this.$t('user.user-updated'));
this.$store.commit("setToken", response.data.access_token);
this.refreshProfile();
this.loading = false;
this.$store.dispatch("requestUserData");
}
}, },
async changePassword() { async changePassword() {
this.paswordLoading = true; this.paswordLoading = true;
@ -215,7 +221,13 @@ export default {
}; };
if (this.$refs.passChange.validate()) { if (this.$refs.passChange.validate()) {
await api.users.changePassword(this.user.id, data); const response = await api.users.changePassword(this.user.id, data);
if (response.status != 200) {
utils.notify.error(this.$t('user.existing-password-does-not-match'));
} else {
utils.notify.success(this.$t('user.password-updated'));
this.$emit("refresh");
}
} }
this.paswordLoading = false; this.paswordLoading = false;
}, },

View file

@ -44,6 +44,7 @@
<script> <script>
const NEW_PAGE_EVENT = "refresh-page"; const NEW_PAGE_EVENT = "refresh-page";
import { api } from "@/api"; import { api } from "@/api";
import utils from "@/utils";
import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector"; import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector";
export default { export default {
components: { components: {
@ -82,14 +83,25 @@ export default {
this.$refs.categoryFormSelector.setInit(this.page.categories); this.$refs.categoryFormSelector.setInit(this.page.categories);
}, },
async submitForm() { async submitForm() {
let response, sucessMessage, errorMessage;
if (this.create) { if (this.create) {
await api.siteSettings.createPage(this.page); response = await api.siteSettings.createPage(this.page);
sucessMessage = this.$t('page.new-page-created');
errorMessage = this.$t('page.page-creation-failed');
} else { } else {
await api.siteSettings.updatePage(this.page); response = await api.siteSettings.updatePage(this.page);
sucessMessage = this.$t('page.page-updated');
errorMessage = this.$t('page.page-update-failed');
}
if (response.status != 200) {
utils.notify.error(errorMessage);
} else {
utils.notify.success(sucessMessage);
this.pageDialog = false;
this.page.categories = [];
this.$emit(NEW_PAGE_EVENT);
} }
this.pageDialog = false;
this.page.categories = [];
this.$emit(NEW_PAGE_EVENT);
}, },
}, },
}; };

View file

@ -65,6 +65,7 @@
import draggable from "vuedraggable"; import draggable from "vuedraggable";
import CreatePageDialog from "./CreatePageDialog"; import CreatePageDialog from "./CreatePageDialog";
import { api } from "@/api"; import { api } from "@/api";
import utils from "@/utils";
export default { export default {
components: { components: {
draggable, draggable,
@ -109,9 +110,14 @@ export default {
element.position = index; element.position = index;
}); });
await api.siteSettings.updateAllPages(this.customPages); const response = await api.siteSettings.updateAllPages(this.customPages);
if (response.status != 200) {
utils.notify.error(this.$t('page.pages-update-failed'));
} else {
utils.notify.success(this.$t('page.pages-updated'));
this.getPages();
}
this.getPages();
}, },
editPage(index) { editPage(index) {
this.editPageData.data = this.customPages[index]; this.editPageData.data = this.customPages[index];

View file

@ -150,6 +150,7 @@ import { api } from "@/api";
import LanguageSelector from "@/components/FormHelpers/LanguageSelector"; import LanguageSelector from "@/components/FormHelpers/LanguageSelector";
import draggable from "vuedraggable"; import draggable from "vuedraggable";
import NewCategoryTagDialog from "@/components/UI/Dialogs/NewCategoryTagDialog.vue"; import NewCategoryTagDialog from "@/components/UI/Dialogs/NewCategoryTagDialog.vue";
import utils from "@/utils";
export default { export default {
components: { components: {
@ -213,8 +214,13 @@ export default {
writeLang(val) { writeLang(val) {
this.settings.language = val; this.settings.language = val;
}, },
deleteCategoryfromDatabase(category) { async deleteCategoryfromDatabase(category) {
api.categories.delete(category); const response = await api.categories.delete(category);
if (response.status != 200) {
utils.notify.error(this.$t('settings.category-deletion-failed'));
} else {
utils.notify.success(this.$t('settings.category-deleted'));
}
}, },
async getOptions() { async getOptions() {
this.settings = await api.siteSettings.get(); this.settings = await api.siteSettings.get();
@ -223,7 +229,12 @@ export default {
this.settings.categories.splice(index, 1); this.settings.categories.splice(index, 1);
}, },
async saveSettings() { async saveSettings() {
await api.siteSettings.update(this.settings); const response = await api.siteSettings.update(this.settings);
if (response.status != 200) {
utils.notify.error(this.$t('settings.settings-update-failed'));
} else {
utils.notify.success(this.$t('settings.settings-updated'));
}
this.getOptions(); this.getOptions();
}, },
}, },

View file

@ -46,6 +46,7 @@
<script> <script>
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog"; import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
import { api } from "@/api"; import { api } from "@/api";
import utils from "@/utils";
const DELETE_EVENT = "delete"; const DELETE_EVENT = "delete";
const APPLY_EVENT = "apply"; const APPLY_EVENT = "apply";
@ -70,11 +71,15 @@ export default {
}, },
async deleteSelectedTheme() { async deleteSelectedTheme() {
//Delete Theme from DB //Delete Theme from DB
await api.themes.delete(this.theme.name); const response = await api.themes.delete(this.theme.name);
if (response.status != 200) {
//Get the new list of available from DB utils.notify.error(this.$t('settings.theme.error-deleting-theme'));
this.availableThemes = await api.themes.requestAll(); } else {
this.$emit(DELETE_EVENT); utils.notify.success(this.$t('settings.theme.theme-deleted'));
//Get the new list of available from DB
this.availableThemes = await api.themes.requestAll();
this.$emit(DELETE_EVENT);
}
}, },
async saveThemes() { async saveThemes() {
this.$store.commit("setTheme", this.theme); this.$store.commit("setTheme", this.theme);

View file

@ -138,6 +138,7 @@ import { api } from "@/api";
import ColorPickerDialog from "@/components/FormHelpers/ColorPickerDialog"; import ColorPickerDialog from "@/components/FormHelpers/ColorPickerDialog";
import NewThemeDialog from "./NewThemeDialog"; import NewThemeDialog from "./NewThemeDialog";
import ThemeCard from "./ThemeCard"; import ThemeCard from "./ThemeCard";
import utils from "@/utils";
export default { export default {
components: { components: {
@ -171,9 +172,14 @@ 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.status != 201) {
this.$store.commit("setTheme", NewThemeDialog); utils.notify.error(this.$t('settings.theme.error-creating-theme-see-log-file'));
} else {
utils.notify.success(this.$t('settings.theme.theme-saved'));
this.availableThemes.push(NewThemeDialog);
this.$store.commit("setTheme", NewThemeDialog);
}
}, },
setStoresDarkMode() { setStoresDarkMode() {
this.$store.commit("setDarkMode", this.selectedDarkMode); this.$store.commit("setDarkMode", this.selectedDarkMode);
@ -182,10 +188,15 @@ 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() { async saveThemes() {
await api.themes.update( const response = await api.themes.update(
this.selectedTheme.name, this.selectedTheme.name,
this.selectedTheme.colors this.selectedTheme.colors
); );
if (response.status != 200) {
utils.notify.error(this.$t('settings.theme.error-updating-theme'));
} else {
utils.notify.success(this.$t('settings.theme.theme-updated'));
}
}, },
}, },
}; };

View file

@ -128,8 +128,13 @@ export default {
this.requestMeals(); this.requestMeals();
}, },
async deletePlan(id) { async deletePlan(id) {
await api.mealPlans.delete(id); const response = await api.mealPlans.delete(id);
this.requestMeals(); if (response.status != 200) {
utils.notify.error(this.$t('meal-plan.mealplan-deletion-failed'));
} else {
utils.notify.success(this.$t('meal-plan.mealplan-deleted'));
this.requestMeals();
}
}, },
openShoppingList(id) { openShoppingList(id) {
this.$refs.shoppingList.openDialog(id); this.$refs.shoppingList.openDialog(id);

View file

@ -42,6 +42,7 @@
<script> <script>
import { api } from "@/api"; import { api } from "@/api";
import utils from "@/utils";
import RecipeEditor from "@/components/Recipe/RecipeEditor"; import RecipeEditor from "@/components/Recipe/RecipeEditor";
import VJsoneditor from "v-jsoneditor"; import VJsoneditor from "v-jsoneditor";
@ -101,7 +102,12 @@ 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); const response = await api.recipes.updateImage(slug, this.fileObject);
if (response.status != 200) {
utils.notify.error(this.$t('general.image-upload-failed'));
} else {
utils.notify.success(this.$t('recipe.recipe-image-updated'));
}
} }
this.isLoading = false; this.isLoading = false;

View file

@ -78,6 +78,9 @@ 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 utils from "@/utils";
import store from "@/store";
import { router } from "@/routes";
export default { export default {
components: { components: {
@ -164,8 +167,15 @@ 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.status != 200) {
utils.notify.error(this.$t('recipe.unable-to-delete-recipe'));
} else {
utils.notify.success(this.$t('recipe.recipe-deleted'));
store.dispatch("requestRecentRecipes");
router.push(`/`);
}
}, },
validateRecipe() { validateRecipe() {
if (this.jsonEditor) { if (this.jsonEditor) {
@ -176,7 +186,12 @@ export default {
}, },
async saveImage() { async saveImage() {
if (this.fileObject) { if (this.fileObject) {
await api.recipes.updateImage(this.recipeDetails.slug, this.fileObject); const response = await api.recipes.updateImage(this.recipeDetails.slug, this.fileObject);
if (response.status != 200) {
utils.notify.error(this.$t('general.image-upload-failed'));
} else {
utils.notify.success(this.$t('recipe.recipe-image-updated'));
}
} }
this.imageKey += 1; this.imageKey += 1;
}, },

View file

@ -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")

View file

@ -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

View file

@ -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,7 +22,7 @@ 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)
): ):
@ -31,8 +30,6 @@ def create_meal_plan(
processed_plan = process_meals(session, data) processed_plan = process_meals(session, data)
db.meals.create(session, processed_plan.dict()) db.meals.create(session, processed_plan.dict())
return SnackResponse.success("Mealplan Created")
@router.put("/{plan_id}") @router.put("/{plan_id}")
def update_meal_plan( def update_meal_plan(
@ -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 )

View file

@ -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")

View 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(
@ -41,6 +40,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 )

View file

@ -2,13 +2,12 @@ import shutil
from enum import Enum from enum import Enum
import requests import requests
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
@ -81,9 +80,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):
@ -106,7 +104,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")
@ -133,5 +131,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")

View file

@ -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"])
@ -43,6 +42,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 )

View file

@ -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),

View file

@ -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(

View file

@ -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 )

View file

@ -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")

View file

@ -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
@ -26,7 +25,6 @@ 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()) data = db.users.create(session, new_user.dict())
return SnackResponse.success(f"User Created: {new_user.full_name}", data)
@router.get("", response_model=list[UserOut]) @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 )

View file

@ -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"}

View file

@ -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)

View file

@ -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)