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/";
import axios from "axios";
import utils from "@/utils";
import { store } from "../store";
axios.defaults.headers.common[
"Authorization"
] = `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 = {
post: async function(url, data) {
let response = await axios.post(url, data).catch(function(error) {
if (error.response) {
processResponse(error.response);
return error.response;
}
});
processResponse(response);
return response;
},
put: async function(url, data) {
let response = await axios.put(url, data).catch(function(error) {
if (error.response) {
processResponse(error.response);
return response;
return error.response;
} else return;
});
processResponse(response);
return response;
},
get: async function(url, data) {
let response = await axios.get(url, data).catch(function(error) {
if (error.response) {
processResponse(error.response);
return response;
return error.response;
} else return;
});
processResponse(response);
return response;
},
delete: async function(url, data) {
let response = await axios.delete(url, data).catch(function(error) {
if (error.response) {
processResponse(error.response);
return response;
return error.response;
}
});
processResponse(response);
return response;
},

View file

@ -40,7 +40,7 @@ export const backupAPI = {
* @param {string} 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

View file

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

View file

@ -17,11 +17,11 @@ export const groupAPI = {
},
async create(name) {
let response = await apiReq.post(groupsURLs.create, { name: name });
return response.data;
return response;
},
async delete(id) {
let response = await apiReq.delete(groupsURLs.delete(id));
return response.data;
return response;
},
async current() {
let response = await apiReq.get(groupsURLs.current);
@ -29,6 +29,6 @@ export const groupAPI = {
},
async update(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) {
let response = await apiReq.delete(migrationURLs.delete(folder, file));
return response.data;
return response;
},
async import(folder, file) {
let response = await apiReq.post(migrationURLs.import(folder, file));

View file

@ -1,7 +1,6 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import { store } from "../store";
import { router } from "../main";
const prefix = baseURL + "recipes/";
@ -72,9 +71,7 @@ export const recipeAPI = {
},
async delete(recipeSlug) {
await apiReq.delete(recipeURLs.delete(recipeSlug));
store.dispatch("requestRecentRecipes");
router.push(`/`);
return await apiReq.delete(recipeURLs.delete(recipeSlug));
},
async allSummary(start = 0, limit = 9999) {

View file

@ -20,11 +20,9 @@ export const signupAPI = {
return response.data;
},
async deleteToken(token) {
let response = await apiReq.delete(signUpURLs.deleteToken(token));
return response.data;
return await apiReq.delete(signUpURLs.deleteToken(token));
},
async createUser(token, data) {
let response = await apiReq.post(signUpURLs.createUser(token), data);
return response.data;
return apiReq.post(signUpURLs.createUser(token), data);
},
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -85,6 +85,7 @@
<script>
import { api } from "@/api";
import { validators } from "@/mixins/validators";
import utils from "@/utils";
export default {
mixins: [validators],
data() {
@ -132,18 +133,20 @@ export default {
admin: false,
};
let successUser = false;
if (this.$refs.signUpForm.validate()) {
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;
if (successUser) {
this.$router.push("/");
}
},
},
};

View file

@ -36,8 +36,13 @@ export default {
return utils.getDateAsPythonDate(dateObject);
},
async update() {
await api.mealPlans.update(this.mealPlan.uid, this.mealPlan);
this.$emit("updated");
const response = await api.mealPlans.update(this.mealPlan.uid, this.mealPlan);
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,
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.meals = [];
this.startDate = null;

View file

@ -3,7 +3,7 @@
<v-menu offset-y top nudge-top="6" :close-on-content-click="false">
<template v-slot:activator="{ on, attrs }">
<v-btn color="accent" dark v-bind="attrs" v-on="on">
{{$t('recipe.image')}}
{{$t('general.image')}}
</v-btn>
</template>
<v-card width="400">
@ -46,6 +46,7 @@ const REFRESH_EVENT = "refresh";
const UPLOAD_EVENT = "upload";
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
import { api } from "@/api";
import utils from "@/utils";
export default {
components: {
TheUploadBtn,
@ -64,6 +65,11 @@ export default {
async getImageFromURL() {
this.loading = true;
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);
this.loading = false;
},

View file

@ -16,6 +16,7 @@
<script>
const UPLOAD_EVENT = "uploaded";
import { api } from "@/api";
import utils from "@/utils";
export default {
props: {
post: {
@ -55,8 +56,13 @@ export default {
let formData = new FormData();
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.$emit(UPLOAD_EVENT);
}

View file

@ -34,15 +34,22 @@
"enabled": "Enabled",
"exception": "Exception",
"failed-count": "Failed: {count}",
"failure-uploading-file": "Failure uploading file",
"field-required": "Field Required",
"file-folder-not-found": "File/folder not found",
"file-uploaded": "File uploaded",
"filter": "Filter",
"friday": "Friday",
"get": "Get",
"groups": "Groups",
"image": "Image",
"image-updated": "Image updated",
"image-upload-failed": "Image upload failed",
"import": "Import",
"monday": "Monday",
"name": "Name",
"no": "No",
"not-authorized": "Not authorized",
"ok": "OK",
"options": "Options:",
"random": "Random",
@ -79,6 +86,14 @@
"group": "Group (Beta)",
"meal-planner": "Meal Planner",
"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",
"planner": "Planner",
"quick-week": "Quick Week",
@ -90,6 +105,7 @@
"description": "Migrate data from Chowdown",
"title": "Chowdown"
},
"migration-data-removed": "Migration data removed",
"nextcloud": {
"description": "Migrate data from a Nextcloud Cookbook intance",
"title": "Nextcloud Cookbook"
@ -108,6 +124,12 @@
"page": {
"all-recipes": "All Recipes",
"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"
},
"recipe": {
@ -122,7 +144,6 @@
"fat-content": "Fat Content",
"fiber-content": "Fiber Content",
"grams": "grams",
"image": "Image",
"ingredient": "Ingredient",
"ingredients": "Ingredients",
"instructions": "Instructions",
@ -139,7 +160,9 @@
"perform-time": "Cook Time",
"prep-time": "Prep Time",
"protein-content": "Protein Content",
"recipe-deleted": "Recipe deleted",
"recipe-image": "Recipe Image",
"recipe-image-updated": "Recipe image updated",
"recipe-name": "Recipe Name",
"servings": "Servings",
"sodium-content": "Sodium Content",
@ -148,6 +171,7 @@
"tags": "Tags",
"title": "Title",
"total-time": "Total Time",
"unable-to-delete-recipe": "Unable to Delete Recipe",
"view-recipe": "View Recipe"
},
"search": {
@ -166,19 +190,26 @@
"admin-settings": "Admin Settings",
"available-backups": "Available Backups",
"backup": {
"backup-created-at-response-export_path": "Backup Created at {path}",
"backup-deleted": "Backup deleted",
"backup-tag": "Backup Tag",
"create-heading": "Create a Backup",
"error-creating-backup-see-log-file": "Error Creating Backup. See Log File",
"full-backup": "Full Backup",
"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-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",
"current": "Version:",
"custom-pages": "Custom Pages",
"edit-page": "Edit Page",
"first-day-of-week": "First day of the week",
"group-settings-updated": "Group Settings Updated",
"homepage": {
"all-categories": "All Categories",
"card-per-section": "Card Per Section",
@ -198,6 +229,8 @@
"profile": "Profile",
"remove-existing-entries-matching-imported-entries": "Remove existing entries matching imported entries",
"set-new-time": "Set New Time",
"settings-update-failed": "Settings update failed",
"settings-updated": "Settings updated",
"site-settings": "Site Settings",
"theme": {
"accent": "Accent",
@ -208,6 +241,9 @@
"default-to-system": "Default to system",
"delete-theme": "Delete Theme",
"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",
"light": "Light",
"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.",
"success": "Success",
"theme": "Theme",
"theme-deleted": "Theme deleted",
"theme-name": "Theme Name",
"theme-name-is-required": "Theme Name is required.",
"theme-saved": "Theme Saved",
"theme-settings": "Theme Settings",
"theme-updated": "Theme updated",
"warning": "Warning"
},
"webhooks": {
@ -227,11 +266,14 @@
"webhook-url": "Webhook URL"
}
},
"this": {},
"user": {
"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-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-link-deletion": "Confirm Link Deletion",
"confirm-password": "Confirm Password",
@ -244,12 +286,19 @@
"e-mail-must-be-valid": "E-mail must be valid",
"edit-user": "Edit User",
"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",
"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",
"incorrect-username-or-password": "Incorrect username or password",
"link-id": "Link ID",
"link-name": "Link Name",
"login": "Login",
@ -257,20 +306,34 @@
"new-password": "New Password",
"new-user": "New User",
"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-reset-failed": "Password reset failed",
"password-updated": "Password updated",
"reset-password": "Reset Password",
"sign-in": "Sign in",
"sign-up-links": "Sign Up Links",
"sign-up-token-deleted": "Sign Up Token Deleted",
"total-mealplans": "Total MealPlans",
"total-users": "Total Users",
"upload-photo": "Upload Photo",
"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-created": "User Group Created",
"user-group-creation-failed": "User Group Creation Failed",
"user-id": "User ID",
"user-id-with-value": "User ID: {id}",
"user-password": "User Password",
"user-successfully-logged-in": "User Successfully Logged In",
"user-update-failed": "User update failed",
"user-updated": "User updated",
"users": "Users",
"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>
import ImportDialog from "./ImportDialog";
import { api } from "@/api";
import utils from "@/utils";
export default {
props: {
backups: Array,
@ -68,10 +69,15 @@ export default {
this.$emit("finished", importData);
},
deleteBackup(data) {
async deleteBackup(data) {
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.backupLoading = false;

View file

@ -44,6 +44,7 @@
<script>
import ImportOptions from "./ImportOptions";
import { api } from "@/api";
import utils from "@/utils";
export default {
components: { ImportOptions },
data() {
@ -97,10 +98,15 @@ export default {
templates: this.selectedTemplates,
};
await api.backups.create(data);
const response = await api.backups.create(data);
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) {
if (this.selectedTemplates.includes(templateName)) {

View file

@ -57,6 +57,7 @@
const RENDER_EVENT = "update";
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
import { api } from "@/api";
import utils from "@/utils";
export default {
components: { ConfirmationDialog },
props: {
@ -91,8 +92,28 @@ export default {
this.$refs.deleteGroupConfirm.open();
},
async deleteGroup() {
await api.groups.delete(this.group.id);
this.$emit(RENDER_EVENT);
const response = await api.groups.delete(this.group.id);
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() {
console.log("Close Delete");

View file

@ -86,6 +86,7 @@
import { validators } from "@/mixins/validators";
import { api } from "@/api";
import GroupCard from "./GroupCard";
import utils from "@/utils";
export default {
components: { GroupCard },
mixins: [validators],
@ -104,8 +105,11 @@ export default {
methods: {
async createGroup() {
this.groupLoading = true;
let response = await api.groups.create(this.newGroupName);
if (response.created) {
const response = await api.groups.create(this.newGroupName);
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.groupDialog = false;
this.$store.dispatch("requestAllGroups");

View file

@ -1,7 +1,7 @@
<template>
<v-card outlined class="mt-n1">
<ConfirmationDialog
ref="deleteUserDialog"
ref="deleteTokenDialog"
:title="$t('user.confirm-link-deletion')"
:message="
$t('user.are-you-sure-you-want-to-delete-the-link', {
@ -9,7 +9,7 @@
})
"
icon="mdi-alert"
@confirm="deleteUser"
@confirm="deleteToken"
:width="450"
@close="closeDelete"
/>
@ -109,6 +109,7 @@
<script>
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
import { api } from "@/api";
import utils from "@/utils";
import { validators } from "@/mixins/validators";
export default {
components: { ConfirmationDialog },
@ -181,8 +182,13 @@ export default {
this.links = await api.signUps.getAll();
},
async deleteUser() {
await api.signUps.deleteToken(this.activeId);
async deleteToken() {
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();
},
@ -197,7 +203,7 @@ export default {
this.activeName = item.name;
this.editedIndex = this.links.indexOf(item);
this.editedItem = Object.assign({}, item);
this.$refs.deleteUserDialog.open();
this.$refs.deleteTokenDialog.open();
},
deleteItemConfirm() {

View file

@ -94,7 +94,7 @@
<v-card-actions>
<v-btn color="info" text @click="resetPassword">
Reset Password
{{$t('user.reset-password')}}
</v-btn>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="close">
@ -146,6 +146,7 @@
<script>
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
import { api } from "@/api";
import utils from "@/utils";
import { validators } from "@/mixins/validators";
export default {
components: { ConfirmationDialog },
@ -223,8 +224,20 @@ export default {
},
async deleteUser() {
await api.users.delete(this.activeId);
this.initialize();
const response = await api.users.delete(this.activeId);
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) {
@ -264,17 +277,40 @@ export default {
async save() {
if (this.editedIndex > -1) {
await api.users.update(this.editedItem);
this.close();
this.updateUser();
} else if (this.$refs.newUser.validate()) {
await api.users.create(this.editedItem);
this.close();
this.createUser();
}
await this.initialize();
},
resetPassword() {
api.users.resetPassword(this.editedItem.id);
async resetPassword() {
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>

View file

@ -84,6 +84,7 @@
import { api } from "@/api";
import TimePickerDialog from "@/components/FormHelpers/TimePickerDialog";
import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector";
import utils from "@/utils";
export default {
components: {
TimePickerDialog,
@ -135,9 +136,14 @@ export default {
this.groupSettings.webhookUrls.splice(index, 1);
},
async saveGroupSettings() {
await api.groups.update(this.groupSettings);
await this.$store.dispatch("requestCurrentGroup");
this.getSiteSettings();
const response = await api.groups.update(this.groupSettings);
if (response.status != 200) {
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() {
api.settings.testWebhooks();

View file

@ -69,6 +69,7 @@
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
import { api } from "@/api";
import MigrationDialog from "./MigrationDialog";
import utils from "@/utils";
export default {
props: {
folder: String,
@ -86,9 +87,14 @@ export default {
};
},
methods: {
deleteMigration(file_name) {
api.migrations.delete(this.folder, file_name);
this.$emit("refresh");
async deleteMigration(file_name) {
const response = await api.migrations.delete(this.folder, file_name);
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) {
this.loading = true;

View file

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

View file

@ -44,6 +44,7 @@
<script>
const NEW_PAGE_EVENT = "refresh-page";
import { api } from "@/api";
import utils from "@/utils";
import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector";
export default {
components: {
@ -82,14 +83,25 @@ export default {
this.$refs.categoryFormSelector.setInit(this.page.categories);
},
async submitForm() {
let response, sucessMessage, errorMessage;
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 {
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 CreatePageDialog from "./CreatePageDialog";
import { api } from "@/api";
import utils from "@/utils";
export default {
components: {
draggable,
@ -109,9 +110,14 @@ export default {
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) {
this.editPageData.data = this.customPages[index];

View file

@ -150,6 +150,7 @@ import { api } from "@/api";
import LanguageSelector from "@/components/FormHelpers/LanguageSelector";
import draggable from "vuedraggable";
import NewCategoryTagDialog from "@/components/UI/Dialogs/NewCategoryTagDialog.vue";
import utils from "@/utils";
export default {
components: {
@ -213,8 +214,13 @@ export default {
writeLang(val) {
this.settings.language = val;
},
deleteCategoryfromDatabase(category) {
api.categories.delete(category);
async deleteCategoryfromDatabase(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() {
this.settings = await api.siteSettings.get();
@ -223,7 +229,12 @@ export default {
this.settings.categories.splice(index, 1);
},
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();
},
},

View file

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

View file

@ -138,6 +138,7 @@ import { api } from "@/api";
import ColorPickerDialog from "@/components/FormHelpers/ColorPickerDialog";
import NewThemeDialog from "./NewThemeDialog";
import ThemeCard from "./ThemeCard";
import utils from "@/utils";
export default {
components: {
@ -171,9 +172,14 @@ export default {
* Create the new Theme and select it.
*/
async appendTheme(NewThemeDialog) {
await api.themes.create(NewThemeDialog);
this.availableThemes.push(NewThemeDialog);
this.$store.commit("setTheme", NewThemeDialog);
const response = await api.themes.create(NewThemeDialog);
if (response.status != 201) {
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() {
this.$store.commit("setDarkMode", this.selectedDarkMode);
@ -182,10 +188,15 @@ export default {
* This will save the current colors and make the selected theme live.
*/
async saveThemes() {
await api.themes.update(
const response = await api.themes.update(
this.selectedTheme.name,
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();
},
async deletePlan(id) {
await api.mealPlans.delete(id);
this.requestMeals();
const response = await api.mealPlans.delete(id);
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) {
this.$refs.shoppingList.openDialog(id);

View file

@ -42,6 +42,7 @@
<script>
import { api } from "@/api";
import utils from "@/utils";
import RecipeEditor from "@/components/Recipe/RecipeEditor";
import VJsoneditor from "v-jsoneditor";
@ -101,7 +102,12 @@ export default {
let slug = await api.recipes.create(this.recipeDetails);
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;

View file

@ -78,6 +78,9 @@ import RecipeEditor from "@/components/Recipe/RecipeEditor";
import RecipeTimeCard from "@/components/Recipe/RecipeTimeCard.vue";
import EditorButtonRow from "@/components/Recipe/EditorButtonRow";
import { user } from "@/mixins/user";
import utils from "@/utils";
import store from "@/store";
import { router } from "@/routes";
export default {
components: {
@ -164,8 +167,15 @@ export default {
return api.recipes.recipeImage(image) + "&rnd=" + this.imageKey;
}
},
deleteRecipe() {
api.recipes.delete(this.recipeDetails.slug);
async deleteRecipe() {
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() {
if (this.jsonEditor) {
@ -176,7 +186,12 @@ export default {
},
async saveImage() {
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;
},

View file

@ -2,13 +2,12 @@ import operator
import shutil
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.security import create_file_token
from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user, validate_file_token
from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup
from mealie.schema.snackbar import SnackResponse
from mealie.services.backups import imports
from mealie.services.backups.exports import backup_all
from sqlalchemy.orm.session import Session
@ -31,30 +30,27 @@ def available_imports():
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)):
"""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:
return SnackResponse.success("Backup Created at " + export_path)
except:
HTTPException(
status_code=400,
detail=SnackResponse.error("Error Creating Backup. See Log File"),
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,
)
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(...)):
""" Upload a .zip File to later be imported into Mealie """
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:
shutil.copyfileobj(archive.file, buffer)
if dest.is_file:
return SnackResponse.success("Backup uploaded")
else:
return SnackResponse.error("Failure uploading file")
if not dest.is_file:
raise HTTPException( status.HTTP_400_BAD_REQUEST )
@router.get("/{file_name}/download")
@ -76,7 +71,7 @@ async def download_backup_file(file_name: str):
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)):
""" 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):
""" 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:
app_dirs.BACKUP_DIR.joinpath(file_name).unlink()
file_path.unlink()
except:
HTTPException(
status_code=400,
detail=SnackResponse.error("Unable to Delete Backup. See Log File"),
)
return SnackResponse.error(f"{file_name} Deleted")
raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR )

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.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.snackbar import SnackResponse
from mealie.schema.user import GroupBase, GroupInDB, UpdateGroup, UserInDB
from sqlalchemy.orm.session import Session
@ -30,7 +29,7 @@ async def get_current_user_group(
return db.groups.get(session, current_user.group, "name")
@router.post("")
@router.post("", status_code=status.HTTP_201_CREATED)
async def create_group(
group_data: GroupBase,
current_user=Depends(get_current_user),
@ -40,9 +39,8 @@ async def create_group(
try:
db.groups.create(session, group_data.dict())
return SnackResponse.success("User Group Created", {"created": True})
except:
return SnackResponse.error("User Group Creation Failed")
raise HTTPException( status.HTTP_400_BAD_REQUEST )
@router.put("/{id}")
@ -55,8 +53,6 @@ async def update_group_data(
""" Updates a User Group """
db.groups.update(session, id, group_data.dict())
return SnackResponse.success("Group Settings Updated")
@router.delete("/{id}")
async def delete_user_group(
@ -65,16 +61,23 @@ async def delete_user_group(
""" Removes a user group from the database """
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)
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 == []:
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)
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.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.meal import MealPlanIn, MealPlanInDB
from mealie.schema.snackbar import SnackResponse
from mealie.schema.user import GroupInDB, UserInDB
from mealie.services.image import image
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)
@router.post("/create")
@router.post("/create", status_code=status.HTTP_201_CREATED)
def create_meal_plan(
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)
db.meals.create(session, processed_plan.dict())
return SnackResponse.success("Mealplan Created")
@router.put("/{plan_id}")
def update_meal_plan(
@ -44,25 +41,28 @@ def update_meal_plan(
""" Updates a meal plan based off ID """
processed_plan = process_meals(session, meal_plan)
processed_plan = MealPlanInDB(uid=plan_id, **processed_plan.dict())
db.meals.update(session, plan_id, processed_plan.dict())
return SnackResponse.info("Mealplan Updated")
try:
db.meals.update(session, plan_id, processed_plan.dict())
except:
raise HTTPException( status.HTTP_400_BAD_REQUEST )
@router.delete("/{plan_id}")
def delete_meal_plan(plan_id, session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
""" Removes a meal plan from the database """
db.meals.delete(session, plan_id)
return SnackResponse.error("Mealplan Deleted")
try:
db.meals.delete(session, plan_id)
except:
raise HTTPException( status.HTTP_400_BAD_REQUEST )
@router.get("/this-week", response_model=MealPlanInDB)
def get_this_week(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)):
""" Returns the meal plan data for this week """
return db.groups.get_meals(session, current_user.group)[0]
plans = db.groups.get_meals(session, current_user.group)
if plans:
return plans[0]
@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")
recipe = get_todays_meal(session, group_in_db)
return recipe.slug
if recipe:
return recipe.slug
@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:
recipe_image = image.read_image(recipe.slug, image_type=image.IMG_OPTIONS.ORIGINAL_IMAGE)
else:
raise HTTPException(404, "no meal for today")
raise HTTPException( status.HTTP_404_NOT_FOUND )
if recipe_image:
return FileResponse(recipe_image)
else:
raise HTTPException(404, "file not found")
raise HTTPException( status.HTTP_404_NOT_FOUND )

View file

@ -2,12 +2,11 @@ import operator
import shutil
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.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.migration import MigrationFile, Migrations
from mealie.schema.snackbar import SnackResponse
from mealie.services.migrations import migration
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)
@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):
""" 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():
shutil.rmtree(remove_path)
else:
SnackResponse.error("File/Folder not found.")
return SnackResponse.error(f"Migration Data Remove: {remove_path.absolute()}")
raise HTTPException( status.HTTP_400_BAD_REQUEST )
@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(...)):
""" Upload a .zip File to later be imported into Mealie """
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:
shutil.copyfileobj(archive.file, buffer)
if dest.is_file:
return SnackResponse.success("Migration data uploaded")
else:
return SnackResponse.error("Failure uploading file")
if not dest.is_file:
raise HTTPException( status.HTTP_400_BAD_REQUEST )

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.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.category import CategoryIn, RecipeCategoryResponse
from mealie.schema.snackbar import SnackResponse
from sqlalchemy.orm.session import Session
router = APIRouter(
@ -41,6 +40,7 @@ async def delete_recipe_category(
category does not impact a recipe. The category will be removed
from any recipes that contain it"""
db.categories.delete(session, category)
return SnackResponse.error(f"Category Deleted: {category}")
try:
db.categories.delete(session, category)
except:
raise HTTPException( status.HTTP_400_BAD_REQUEST )

View file

@ -2,13 +2,12 @@ import shutil
from enum import Enum
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 mealie.db.database import db
from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.recipe import Recipe, RecipeURLIn
from mealie.schema.snackbar import SnackResponse
from mealie.services.image.image import IMG_OPTIONS, delete_image, read_image, rename_image, scrape_image, write_image
from mealie.services.scraper.scraper import create_from_url
from sqlalchemy.orm.session import Session
@ -81,9 +80,8 @@ def delete_recipe(
db.recipes.delete(session, recipe_slug)
delete_image(recipe_slug)
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):
@ -106,7 +104,7 @@ async def get_recipe_img(recipe_slug: str, image_type: ImageType = ImageType.ori
if recipe_image:
return FileResponse(recipe_image)
else:
raise HTTPException(404, "file not found")
raise HTTPException( status.HTTP_404_NOT_FOUND )
@router.put("/{recipe_slug}/image")
@ -133,5 +131,3 @@ def scrape_image_url(
""" Removes an existing image and replaces it with the incoming file. """
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.routes.deps import get_current_user
from mealie.schema.category import RecipeTagResponse, TagIn
from mealie.schema.snackbar import SnackResponse
from sqlalchemy.orm.session import Session
router = APIRouter(tags=["Recipes"])
@ -43,6 +42,7 @@ async def delete_recipe_tag(
tag does not impact a recipe. The tag will be removed
from any recipes that contain it"""
db.tags.delete(session, tag)
return SnackResponse.error(f"Tag Deleted: {tag}")
try:
db.tags.delete(session, 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.routes.deps import get_current_user
from mealie.schema.settings import CustomPageBase, CustomPageOut
from mealie.schema.snackbar import SnackResponse
from mealie.schema.user import UserInDB
from sqlalchemy.orm.session import Session
@ -29,8 +28,6 @@ async def create_new_page(
db.custom_pages.create(session, new_page.dict())
return SnackResponse.success("New Page Created")
@router.put("")
async def update_multiple_pages(
@ -41,7 +38,6 @@ async def update_multiple_pages(
""" Update multiple custom pages """
for page in pages:
db.custom_pages.update(session, page.id, page.dict())
return SnackResponse.success("Pages Updated")
@router.get("/{id}")
@ -57,7 +53,7 @@ async def get_single_page(
@router.put("/{id}")
async def update_single_age(
async def update_single_page(
data: CustomPageOut,
id: int,
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.routes.deps import get_current_user
from mealie.schema.settings import SiteSettings
from mealie.schema.snackbar import SnackResponse
from mealie.schema.user import GroupInDB, UserInDB
from mealie.utils.post_webhooks import post_webhooks
from sqlalchemy.orm.session import Session
@ -27,8 +26,6 @@ def update_settings(
""" Returns Site Settings """
db.settings.update(session, 1, data.dict())
return SnackResponse.success("Settings Updated")
@router.post("/webhooks/test")
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.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.snackbar import SnackResponse
from mealie.schema.theme import SiteTheme
from sqlalchemy.orm.session import Session
@ -16,12 +15,11 @@ def get_all_themes(session: Session = Depends(generate_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)):
""" Creates a site color theme database entry """
db.themes.create(session, data.dict())
return SnackResponse.success("Theme Saved")
@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)
@router.put("/themes/{theme_name}")
@router.put("/themes/{theme_name}", status_code=status.HTTP_200_OK)
def update_theme(
theme_name: str,
data: SiteTheme,
@ -40,12 +38,11 @@ def update_theme(
""" Update a theme database entry """
db.themes.update(session, theme_name, data.dict())
return SnackResponse.info(f"Theme Updated: {theme_name}")
@router.delete("/themes/{theme_name}")
@router.delete("/themes/{theme_name}", status_code=status.HTTP_200_OK)
def delete_theme(theme_name: str, session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
""" Deletes theme from the database """
db.themes.delete(session, theme_name)
return SnackResponse.error(f"Theme Deleted: {theme_name}")
try:
db.themes.delete(session, 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.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.snackbar import SnackResponse
from mealie.schema.user import UserInDB
from sqlalchemy.orm.session import Session
@ -28,15 +27,11 @@ def get_token(
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token = security.create_access_token(dict(sub=email))
return SnackResponse.success(
"User Successfully Logged In",
{"access_token": access_token, "token_type": "bearer"},
)
return {"access_token": access_token, "token_type": "bearer"}
@router.get("/refresh")

View file

@ -1,7 +1,7 @@
import shutil
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 mealie.core import security
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.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.snackbar import SnackResponse
from mealie.schema.user import ChangePassword, UserBase, UserIn, UserInDB, UserOut
from sqlalchemy.orm.session import Session
@ -26,7 +25,6 @@ async def create_user(
new_user.password = get_password_hash(new_user.password)
data = db.users.create(session, new_user.dict())
return SnackResponse.success(f"User Created: {new_user.full_name}", data)
@router.get("", response_model=list[UserOut])
@ -35,10 +33,10 @@ async def get_all_users(
session: Session = Depends(generate_session),
):
if current_user.admin:
return db.users.get_all(session)
else:
return {"details": "user not authorized"}
if not current_user.admin:
raise HTTPException( status.HTTP_403_FORBIDDEN )
return db.users.get_all(session)
@router.get("/self", response_model=UserOut)
@ -68,7 +66,6 @@ async def reset_user_password(
new_password = get_password_hash(settings.DEFAULT_PASSWORD)
db.users.update_password(session, id, new_password)
return SnackResponse.success("Users Password Reset")
@router.put("/{id}")
@ -85,8 +82,7 @@ async def update_user(
if current_user.id == id:
access_token = security.create_access_token(data=dict(sub=new_data.email))
token = {"access_token": access_token, "token_type": "bearer"}
return SnackResponse.success("User Updated", token)
return token
@router.get("/{id}/image")
@ -121,10 +117,8 @@ async def update_user_image(
with dest.open("wb") as buffer:
shutil.copyfileobj(profile_image.file, buffer)
if dest.is_file:
return SnackResponse.success("File uploaded")
else:
return SnackResponse.error("Failure uploading file")
if not dest.is_file:
raise HTTPException( status.HTTP_500_INTERNAL_SERVER_ERROR )
@router.put("/{id}/password")
@ -139,12 +133,12 @@ async def update_password(
match_passwords = verify_password(password_change.current_password, current_user.password)
match_id = current_user.id == id
if match_passwords and match_id:
new_password = get_password_hash(password_change.new_password)
db.users.update_password(session, id, new_password)
return SnackResponse.success("Password Updated")
else:
return SnackResponse.error("Existing password does not match")
if not ( match_passwords and match_id ):
raise HTTPException( status.HTTP_401_UNAUTHORIZED )
new_password = get_password_hash(password_change.new_password)
db.users.update_password(session, id, new_password)
@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"""
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:
db.users.delete(session, id)
return SnackResponse.error("User Deleted")
try:
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 mealie.routes.deps import get_current_user
from mealie.schema.sign_up import SignUpIn, SignUpOut, SignUpToken
from mealie.schema.snackbar import SnackResponse
from mealie.schema.user import UserIn, UserInDB
from sqlalchemy.orm.session import Session
@ -33,18 +32,16 @@ async def create_user_sign_up_key(
):
""" Generates a Random Token that a new user can sign up with """
if current_user.admin:
sign_up = {
"token": str(uuid.uuid1().hex),
"name": key_data.name,
"admin": key_data.admin,
}
db_entry = db.sign_ups.create(session, sign_up)
if not current_user.admin:
raise HTTPException( status.HTTP_403_FORBIDDEN )
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}")
@ -58,7 +55,7 @@ async def create_user_with_token(
# Validate Token
db_entry: SignUpOut = db.sign_ups.get(session, token, limit=1)
if not db_entry:
return SnackResponse.error("Invalid Token")
raise HTTPException( status.HTTP_401_UNAUTHORIZED )
# Create User
new_user.admin = db_entry.admin
@ -68,9 +65,6 @@ async def create_user_with_token(
# DeleteToken
db.sign_ups.delete(session, token)
# Respond
return SnackResponse.success(f"User Created: {new_user.full_name}", data)
@router.delete("/{token}")
async def delete_token(
@ -79,8 +73,7 @@ async def delete_token(
session: Session = Depends(generate_session),
):
""" Removed a token from the database """
if current_user.admin:
db.sign_ups.delete(session, token)
return SnackResponse.error("Sign Up Token Deleted")
else:
return {"details", "not authorized"}
if not current_user.admin:
raise HTTPException( status.HTTP_403_FORBIDDEN )
db.sign_ups.delete(session, token)

View file

@ -3,7 +3,6 @@ from typing import Optional
from fastapi import APIRouter, Depends
from mealie.routes.deps import validate_file_token
from mealie.schema.snackbar import SnackResponse
from starlette.responses import FileResponse
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
system. """
print("File Name:", file_path)
if file_path.is_file():
return FileResponse(file_path, media_type="application/octet-stream", filename=file_path.name)
else:
return SnackResponse.error("No File Found")
if not file_path.is_file():
raise HTTPException( status.HTTP_400_BAD_REQUEST )
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)