backend-events + code-cleanup (#395)
* additional server events * sort 'recent recipes' by updated * remove duplicate code * fixes #396 * set color * consolidate tag/category pages * set colors * list unorganized recipes * cleanup old code * remove flash message, switch to global snackbar * cancel to close * cleanup Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
parent
96919319b1
commit
466997febc
31 changed files with 1604 additions and 686 deletions
|
@ -59,7 +59,7 @@
|
||||||
- All images are now converted to .webp for better compression
|
- All images are now converted to .webp for better compression
|
||||||
|
|
||||||
### General
|
### General
|
||||||
- New 'Dark' Theme Packages with Mealie
|
- New 'Dark' Color Theme Packaged with Mealie
|
||||||
- Updated Recipe Card Sections Toolbar
|
- Updated Recipe Card Sections Toolbar
|
||||||
- New Sort Options (They work this time!)
|
- New Sort Options (They work this time!)
|
||||||
- Alphabetical
|
- Alphabetical
|
||||||
|
@ -82,6 +82,7 @@
|
||||||
- Improved styling for search bar in desktop
|
- Improved styling for search bar in desktop
|
||||||
- Improved search layout on mobile
|
- Improved search layout on mobile
|
||||||
- Profile image now shown on all sidebars
|
- Profile image now shown on all sidebars
|
||||||
|
- Switched from Flash Messages to Snackbar (Removed dependency
|
||||||
|
|
||||||
### Behind the Scenes
|
### Behind the Scenes
|
||||||
- Black and Flake8 now run as CI/CD checks
|
- Black and Flake8 now run as CI/CD checks
|
||||||
|
|
1884
frontend/package-lock.json
generated
1884
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -10,7 +10,6 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adapttive/vue-markdown": "^4.0.1",
|
"@adapttive/vue-markdown": "^4.0.1",
|
||||||
"@smartweb/vue-flash-message": "^0.6.10",
|
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"core-js": "^3.9.1",
|
"core-js": "^3.9.1",
|
||||||
"fast-levenshtein": "^3.0.0",
|
"fast-levenshtein": "^3.0.0",
|
||||||
|
@ -31,7 +30,7 @@
|
||||||
"@mdi/font": "^5.9.55",
|
"@mdi/font": "^5.9.55",
|
||||||
"@vue/cli-plugin-babel": "^4.5.11",
|
"@vue/cli-plugin-babel": "^4.5.11",
|
||||||
"@vue/cli-plugin-eslint": "^4.5.11",
|
"@vue/cli-plugin-eslint": "^4.5.11",
|
||||||
"@vue/cli-service": "^4.1.1",
|
"@vue/cli-service": "^4.5.12",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
|
|
|
@ -6,14 +6,15 @@
|
||||||
<v-banner v-if="demo" sticky
|
<v-banner v-if="demo" sticky
|
||||||
><div class="text-center"><b> This is a Demo</b> | Username: changeme@email.com | Password: demo</div></v-banner
|
><div class="text-center"><b> This is a Demo</b> | Username: changeme@email.com | Password: demo</div></v-banner
|
||||||
>
|
>
|
||||||
|
<GlobalSnackbar />
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
</v-main>
|
</v-main>
|
||||||
<FlashMessage :position="'right bottom'"></FlashMessage>
|
|
||||||
</v-app>
|
</v-app>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TheAppBar from "@/components/UI/TheAppBar";
|
import TheAppBar from "@/components/UI/TheAppBar";
|
||||||
|
import GlobalSnackbar from "@/components/UI/GlobalSnackbar";
|
||||||
import Vuetify from "./plugins/vuetify";
|
import Vuetify from "./plugins/vuetify";
|
||||||
import { user } from "@/mixins/user";
|
import { user } from "@/mixins/user";
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ export default {
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
TheAppBar,
|
TheAppBar,
|
||||||
|
GlobalSnackbar,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [user],
|
mixins: [user],
|
||||||
|
@ -71,38 +73,6 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.notify-info-color {
|
|
||||||
border: 1px, solid, var(--v-info-base) !important;
|
|
||||||
border-left: 3px, solid, var(--v-info-base) !important;
|
|
||||||
background-color: var(--v-info-base) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notify-warning-color {
|
|
||||||
border: 1px, solid, var(--v-warning-base) !important;
|
|
||||||
border-left: 3px, solid, var(--v-warning-base) !important;
|
|
||||||
background-color: var(--v-warning-base) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notify-error-color {
|
|
||||||
border: 1px, solid, var(--v-error-base) !important;
|
|
||||||
border-left: 3px, solid, var(--v-error-base) !important;
|
|
||||||
background-color: var(--v-error-base) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notify-success-color {
|
|
||||||
border: 1px, solid, var(--v-success-base) !important;
|
|
||||||
border-left: 3px, solid, var(--v-success-base) !important;
|
|
||||||
background-color: var(--v-success-base) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notify-base {
|
|
||||||
color: white !important;
|
|
||||||
/* min-height: 50px; */
|
|
||||||
margin-right: 60px;
|
|
||||||
margin-bottom: -5px;
|
|
||||||
opacity: 0.9 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
*::-webkit-scrollbar {
|
*::-webkit-scrollbar {
|
||||||
width: 0.25rem;
|
width: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@ const recipeURLs = {
|
||||||
createAsset: slug => `${prefix}${slug}/assets`,
|
createAsset: slug => `${prefix}${slug}/assets`,
|
||||||
recipeImage: slug => `${prefix}${slug}/image`,
|
recipeImage: slug => `${prefix}${slug}/image`,
|
||||||
updateImage: slug => `${prefix}${slug}/image`,
|
updateImage: slug => `${prefix}${slug}/image`,
|
||||||
|
untagged: prefix + "summary/untagged",
|
||||||
|
uncategorized: prefix + "summary/uncategorized ",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const recipeAPI = {
|
export const recipeAPI = {
|
||||||
|
@ -134,6 +136,16 @@ export const recipeAPI = {
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async allUntagged() {
|
||||||
|
const response = await apiReq.get(recipeURLs.untagged);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async allUnategorized() {
|
||||||
|
const response = await apiReq.get(recipeURLs.uncategorized);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
recipeImage(recipeSlug) {
|
recipeImage(recipeSlug) {
|
||||||
return `/api/media/recipes/${recipeSlug}/images/original.webp`;
|
return `/api/media/recipes/${recipeSlug}/images/original.webp`;
|
||||||
},
|
},
|
||||||
|
|
30
frontend/src/components/UI/GlobalSnackbar.vue
Normal file
30
frontend/src/components/UI/GlobalSnackbar.vue
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<template>
|
||||||
|
<div class="text-center ma-2">
|
||||||
|
<v-snackbar v-model="snackbar.open" top :color="snackbar.color" timeout="3500">
|
||||||
|
{{ snackbar.title }}
|
||||||
|
{{ snackbar.text }}
|
||||||
|
|
||||||
|
<template v-slot:action="{ attrs }">
|
||||||
|
<v-btn text v-bind="attrs" @click="snackbar.open = false">
|
||||||
|
{{ $t("general.close") }}
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data: () => ({}),
|
||||||
|
computed: {
|
||||||
|
snackbar: {
|
||||||
|
set(val) {
|
||||||
|
this.$store.commit("setSnackbar", val);
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
return this.$store.getters.getSnackbar;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -106,7 +106,7 @@ export default {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
},
|
},
|
||||||
isValidWebUrl(url) {
|
isValidWebUrl(url) {
|
||||||
let regEx = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)$/gm;
|
let regEx = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,256}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)$/gm;
|
||||||
return regEx.test(url) ? true : "Must be a Valid URL";
|
return regEx.test(url) ? true : "Must be a Valid URL";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,11 +5,9 @@ import store from "./store";
|
||||||
import VueRouter from "vue-router";
|
import VueRouter from "vue-router";
|
||||||
import { router } from "./routes";
|
import { router } from "./routes";
|
||||||
import i18n from "./i18n";
|
import i18n from "./i18n";
|
||||||
import FlashMessage from "@smartweb/vue-flash-message";
|
|
||||||
import "@mdi/font/css/materialdesignicons.css";
|
import "@mdi/font/css/materialdesignicons.css";
|
||||||
import "typeface-roboto/index.css";
|
import "typeface-roboto/index.css";
|
||||||
|
|
||||||
Vue.use(FlashMessage);
|
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<template v-slot:after-heading>
|
<template v-slot:after-heading>
|
||||||
<div class="ml-auto text-right">
|
<div class="ml-auto text-right">
|
||||||
<h2 class="body-3 grey--text font-weight-light">
|
<h2 class="body-3 grey--text font-weight-light">
|
||||||
{{$t('settings.backup-and-exports')}}
|
{{ $t("settings.backup-and-exports") }}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<h3 class="display-2 font-weight-light text--primary">
|
<h3 class="display-2 font-weight-light text--primary">
|
||||||
|
@ -23,15 +23,15 @@
|
||||||
<div class="d-flex row py-3 justify-end">
|
<div class="d-flex row py-3 justify-end">
|
||||||
<TheUploadBtn url="/api/backups/upload" @uploaded="getAvailableBackups">
|
<TheUploadBtn url="/api/backups/upload" @uploaded="getAvailableBackups">
|
||||||
<template v-slot="{ isSelecting, onButtonClick }">
|
<template v-slot="{ isSelecting, onButtonClick }">
|
||||||
<v-btn :loading="isSelecting" class="mx-2" small :color="color" @click="onButtonClick">
|
<v-btn :loading="isSelecting" class="mx-2" small color="info" @click="onButtonClick">
|
||||||
<v-icon left> mdi-cloud-upload </v-icon> {{$t('general.upload')}}
|
<v-icon left> mdi-cloud-upload </v-icon> {{ $t("general.upload") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
</TheUploadBtn>
|
</TheUploadBtn>
|
||||||
<BackupDialog :color="color" />
|
<BackupDialog :color="color" />
|
||||||
|
|
||||||
<v-btn :loading="loading" class="mx-2" small :color="color" @click="createBackup">
|
<v-btn :loading="loading" class="mx-2" small color="success" @click="createBackup">
|
||||||
<v-icon left> mdi-plus </v-icon> {{$t('general.create')}}
|
<v-icon left> mdi-plus </v-icon> {{ $t("general.create") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
<template v-slot:bottom>
|
<template v-slot:bottom>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<template v-slot:after-heading>
|
<template v-slot:after-heading>
|
||||||
<div class="ml-auto text-right">
|
<div class="ml-auto text-right">
|
||||||
<h2 class="body-3 grey--text font-weight-light">
|
<h2 class="body-3 grey--text font-weight-light">
|
||||||
{{$t('settings.events')}}
|
{{ $t("settings.events") }}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<h3 class="display-2 font-weight-light text--primary">
|
<h3 class="display-2 font-weight-light text--primary">
|
||||||
|
@ -13,8 +13,8 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="d-flex row py-3 justify-end">
|
<div class="d-flex row py-3 justify-end">
|
||||||
<v-btn class="mx-2" small :color="color" @click="deleteAll">
|
<v-btn class="mx-2" small color="error lighten-1" @click="deleteAll">
|
||||||
<v-icon left> mdi-notification-clear-all </v-icon> {{$t('general.clear')}}
|
<v-icon left> mdi-notification-clear-all </v-icon> {{ $t("general.clear") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
<template v-slot:bottom>
|
<template v-slot:bottom>
|
||||||
|
@ -69,7 +69,7 @@ export default {
|
||||||
color: "primary",
|
color: "primary",
|
||||||
},
|
},
|
||||||
backup: {
|
backup: {
|
||||||
icon: "mdi-backup-restore",
|
icon: "mdi-database",
|
||||||
color: "primary",
|
color: "primary",
|
||||||
},
|
},
|
||||||
schedule: {
|
schedule: {
|
||||||
|
@ -80,9 +80,13 @@ export default {
|
||||||
icon: "mdi-database-import",
|
icon: "mdi-database-import",
|
||||||
color: "primary",
|
color: "primary",
|
||||||
},
|
},
|
||||||
signup: {
|
user: {
|
||||||
icon: "mdi-account",
|
icon: "mdi-account",
|
||||||
color: "primary",
|
color: "accent",
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
icon: "mdi-account-group-outline",
|
||||||
|
color: "accent",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer class="mx-2"></v-spacer>
|
<v-spacer class="mx-2"></v-spacer>
|
||||||
<v-btn class="my-1 mb-n1" :color="color" @click="createTheme">
|
<v-btn class="my-1 mb-n1" color="success" @click="createTheme">
|
||||||
<v-icon left> mdi-plus </v-icon> {{ $t("general.create") }}
|
<v-icon left> mdi-plus </v-icon> {{ $t("general.create") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
:top="true"
|
:top="true"
|
||||||
>
|
>
|
||||||
<template v-slot:open="{ open }">
|
<template v-slot:open="{ open }">
|
||||||
<v-btn color="primary" class="mr-1" small @click="open">
|
<v-btn color="info" class="mr-1" small @click="open">
|
||||||
<v-icon left>mdi-lock</v-icon>
|
<v-icon left>mdi-lock</v-icon>
|
||||||
Change Password
|
Change Password
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
@ -99,7 +99,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
|
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
|
||||||
import StatCard from "@/components/UI/StatCard";
|
import StatCard from "@/components/UI/StatCard";
|
||||||
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
|
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<v-card outlined class="mt-n1">
|
<v-card outlined class="mt-n1">
|
||||||
<base-dialog
|
<BaseDialog
|
||||||
ref="renameDialog"
|
ref="renameDialog"
|
||||||
title-icon="mdi-tag"
|
title-icon="mdi-tag"
|
||||||
:title="renameTarget.title"
|
:title="renameTarget.title"
|
||||||
|
:submit-text="$t('general.update')"
|
||||||
modal-width="800"
|
modal-width="800"
|
||||||
@submit="renameFromDialog(renameTarget.slug, renameTarget.newName)"
|
@submit="renameFromDialog(renameTarget.slug, renameTarget.newName)"
|
||||||
>
|
>
|
||||||
|
@ -32,7 +33,7 @@
|
||||||
:tags="recipe.tags"
|
:tags="recipe.tags"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</base-dialog>
|
</BaseDialog>
|
||||||
|
|
||||||
<div class="d-flex justify-center align-center pa-2 flex-wrap">
|
<div class="d-flex justify-center align-center pa-2 flex-wrap">
|
||||||
<new-category-tag-dialog ref="newDialog" :tag-dialog="isTags">
|
<new-category-tag-dialog ref="newDialog" :tag-dialog="isTags">
|
||||||
|
|
|
@ -13,38 +13,45 @@
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-btn-toggle>
|
</v-btn-toggle>
|
||||||
<v-spacer v-if="!isMobile"> </v-spacer>
|
<v-spacer v-if="!isMobile"> </v-spacer>
|
||||||
|
|
||||||
<FuseSearchBar :raw-data="allItems" @results="filterItems" :search="searchString">
|
|
||||||
<v-text-field
|
|
||||||
v-model="searchString"
|
|
||||||
clearable
|
|
||||||
solo
|
|
||||||
dense
|
|
||||||
class="mx-2"
|
|
||||||
hide-details
|
|
||||||
single-line
|
|
||||||
:placeholder="$t('search.search')"
|
|
||||||
prepend-inner-icon="mdi-magnify"
|
|
||||||
>
|
|
||||||
</v-text-field>
|
|
||||||
</FuseSearchBar>
|
|
||||||
</div>
|
</div>
|
||||||
|
<v-card-text>
|
||||||
|
<CardSection :sortable="true" title="Unorganized" :recipes="shownRecipes" @sort="assignSorted" />
|
||||||
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FuseSearchBar from "@/components/UI/Search/FuseSearchBar";
|
import { api } from "@/api";
|
||||||
|
import CardSection from "@/components/UI/CardSection";
|
||||||
export default {
|
export default {
|
||||||
components: { FuseSearchBar },
|
components: {
|
||||||
|
// FuseSearchBar,
|
||||||
|
CardSection,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
buttonToggle: 0,
|
buttonToggle: 0,
|
||||||
allItems: [],
|
tagRecipes: [],
|
||||||
|
categoryRecipes: [],
|
||||||
searchString: "",
|
searchString: "",
|
||||||
searchResults: [],
|
sortedResults: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
shownRecipes() {
|
||||||
|
if (this.sortedResults.length > 0) {
|
||||||
|
return this.sortedResults;
|
||||||
|
} else {
|
||||||
|
switch (this.filter) {
|
||||||
|
case "category":
|
||||||
|
return this.categoryRecipes;
|
||||||
|
case "tag":
|
||||||
|
return this.tagRecipes;
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
isMobile() {
|
isMobile() {
|
||||||
return this.$vuetify.breakpoint.name === "xs";
|
return this.$vuetify.breakpoint.name === "xs";
|
||||||
},
|
},
|
||||||
|
@ -60,10 +67,22 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.refreshUnorganized();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
filterItems(val) {
|
filterItems(val) {
|
||||||
this.searchResults = val.map(x => x.item);
|
this.searchResults = val.map(x => x.item);
|
||||||
},
|
},
|
||||||
|
async refreshUnorganized() {
|
||||||
|
this.loading = true;
|
||||||
|
this.tagRecipes = await api.recipes.allUntagged();
|
||||||
|
this.categoryRecipes = await api.recipes.allUnategorized();
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
assignSorted(val) {
|
||||||
|
this.sortedResults = val;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -22,6 +22,12 @@ export default {
|
||||||
currentCategory() {
|
currentCategory() {
|
||||||
return this.$route.params.category;
|
return this.$route.params.category;
|
||||||
},
|
},
|
||||||
|
currentTag() {
|
||||||
|
return this.$route.params.tag;
|
||||||
|
},
|
||||||
|
TagOrCategory() {
|
||||||
|
return this.currentCategory || this.currentTag;
|
||||||
|
},
|
||||||
shownRecipes() {
|
shownRecipes() {
|
||||||
if (this.sortedResults.length > 0) {
|
if (this.sortedResults.length > 0) {
|
||||||
return this.sortedResults;
|
return this.sortedResults;
|
||||||
|
@ -31,7 +37,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
async currentCategory() {
|
async TagOrCategory() {
|
||||||
this.sortedResults = [];
|
this.sortedResults = [];
|
||||||
this.getRecipes();
|
this.getRecipes();
|
||||||
},
|
},
|
||||||
|
@ -42,7 +48,14 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async getRecipes() {
|
async getRecipes() {
|
||||||
let data = await api.categories.getRecipesInCategory(this.currentCategory);
|
if (!this.TagOrCategory === null) return;
|
||||||
|
|
||||||
|
let data = {};
|
||||||
|
if (this.currentCategory) {
|
||||||
|
data = await api.categories.getRecipesInCategory(this.TagOrCategory);
|
||||||
|
} else {
|
||||||
|
data = await api.tags.getRecipesInTag(this.TagOrCategory);
|
||||||
|
}
|
||||||
this.title = data.name;
|
this.title = data.name;
|
||||||
this.recipes = data.recipes;
|
this.recipes = data.recipes;
|
||||||
},
|
},
|
|
@ -69,10 +69,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
async buildPage() {
|
async buildPage() {
|
||||||
this.page = await api.siteSettings.getPage(this.pageSlug);
|
this.page = await api.siteSettings.getPage(this.pageSlug);
|
||||||
},
|
this.tab = this.page.categories[0];
|
||||||
filterRecipe(slug) {
|
|
||||||
const storeCategory = this.recipeStore.find(element => element.slug === slug);
|
|
||||||
return storeCategory ? storeCategory.recipes : [];
|
|
||||||
},
|
},
|
||||||
sortRecipes(sortedRecipes, destKey) {
|
sortRecipes(sortedRecipes, destKey) {
|
||||||
this.page.categories[destKey].recipes = sortedRecipes;
|
this.page.categories[destKey].recipes = sortedRecipes;
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
<template>
|
|
||||||
<v-container>
|
|
||||||
<CardSection :sortable="true" :title="title" :recipes="shownRecipes" @sort="assignSorted" />
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { api } from "@/api";
|
|
||||||
import CardSection from "@/components/UI/CardSection";
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
CardSection,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
title: "",
|
|
||||||
recipes: [],
|
|
||||||
sortedResults: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
currentTag() {
|
|
||||||
return this.$route.params.tag;
|
|
||||||
},
|
|
||||||
shownRecipes() {
|
|
||||||
if (this.sortedResults.length > 0) {
|
|
||||||
return this.sortedResults;
|
|
||||||
} else {
|
|
||||||
return this.recipes;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
async currentTag() {
|
|
||||||
this.getRecipes();
|
|
||||||
this.sortedResults = [];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.getRecipes();
|
|
||||||
this.sortedResults = [];
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async getRecipes() {
|
|
||||||
let data = await api.tags.getRecipesInTag(this.currentTag);
|
|
||||||
this.title = data.name;
|
|
||||||
this.recipes = data.recipes;
|
|
||||||
},
|
|
||||||
assignSorted(val) {
|
|
||||||
console.log(val);
|
|
||||||
this.sortedResults = val.slice();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style></style>
|
|
|
@ -2,15 +2,14 @@ import ViewRecipe from "@/pages/Recipe/ViewRecipe";
|
||||||
import NewRecipe from "@/pages/Recipe/NewRecipe";
|
import NewRecipe from "@/pages/Recipe/NewRecipe";
|
||||||
import CustomPage from "@/pages/Recipes/CustomPage";
|
import CustomPage from "@/pages/Recipes/CustomPage";
|
||||||
import AllRecipes from "@/pages/Recipes/AllRecipes";
|
import AllRecipes from "@/pages/Recipes/AllRecipes";
|
||||||
import CategoryPage from "@/pages/Recipes/CategoryPage";
|
import CategoryTagPage from "@/pages/Recipes/CategoryTagPage";
|
||||||
import TagPage from "@/pages/Recipes/TagPage";
|
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
|
|
||||||
export const recipeRoutes = [
|
export const recipeRoutes = [
|
||||||
// Recipes
|
// Recipes
|
||||||
{ path: "/recipes/all", component: AllRecipes },
|
{ path: "/recipes/all", component: AllRecipes },
|
||||||
{ path: "/recipes/tag/:tag", component: TagPage },
|
{ path: "/recipes/tag/:tag", component: CategoryTagPage },
|
||||||
{ path: "/recipes/category/:category", component: CategoryPage },
|
{ path: "/recipes/category/:category", component: CategoryTagPage },
|
||||||
// Misc
|
// Misc
|
||||||
{ path: "/new/", component: NewRecipe },
|
{ path: "/new/", component: NewRecipe },
|
||||||
{ path: "/pages/:customPage", component: CustomPage },
|
{ path: "/pages/:customPage", component: CustomPage },
|
||||||
|
|
|
@ -7,6 +7,7 @@ import language from "./modules/language";
|
||||||
import siteSettings from "./modules/siteSettings";
|
import siteSettings from "./modules/siteSettings";
|
||||||
import recipes from "./modules/recipes";
|
import recipes from "./modules/recipes";
|
||||||
import groups from "./modules/groups";
|
import groups from "./modules/groups";
|
||||||
|
import snackbar from "./modules/snackbar";
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ const store = new Vuex.Store({
|
||||||
siteSettings,
|
siteSettings,
|
||||||
groups,
|
groups,
|
||||||
recipes,
|
recipes,
|
||||||
|
snackbar,
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
// All Recipe Data Store
|
// All Recipe Data Store
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
|
import { recipe } from "@/utils/recipe";
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
recentRecipes: [],
|
recentRecipes: [],
|
||||||
|
@ -36,7 +37,6 @@ const mutations = {
|
||||||
const actions = {
|
const actions = {
|
||||||
async requestRecentRecipes() {
|
async requestRecentRecipes() {
|
||||||
const payload = await api.recipes.allSummary(0, 30);
|
const payload = await api.recipes.allSummary(0, 30);
|
||||||
payload.sort((a, b) => (a.dateAdded > b.dateAdded ? -1 : 1));
|
|
||||||
const hash = Object.fromEntries(payload.map(e => [e.id, e]));
|
const hash = Object.fromEntries(payload.map(e => [e.id, e]));
|
||||||
this.commit("setRecentRecipes", hash);
|
this.commit("setRecentRecipes", hash);
|
||||||
},
|
},
|
||||||
|
@ -60,7 +60,11 @@ const actions = {
|
||||||
const getters = {
|
const getters = {
|
||||||
getAllRecipes: state => Object.values(state.allRecipes),
|
getAllRecipes: state => Object.values(state.allRecipes),
|
||||||
getAllRecipesHash: state => state.allRecipes,
|
getAllRecipesHash: state => state.allRecipes,
|
||||||
getRecentRecipes: state => Object.values(state.recentRecipes),
|
getRecentRecipes: state => {
|
||||||
|
let list = Object.values(state.recentRecipes);
|
||||||
|
recipe.sortByUpdated(list);
|
||||||
|
return list;
|
||||||
|
},
|
||||||
getRecentRecipesHash: state => state.recentRecipes,
|
getRecentRecipesHash: state => state.recentRecipes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
23
frontend/src/store/modules/snackbar.js
Normal file
23
frontend/src/store/modules/snackbar.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
const state = {
|
||||||
|
snackbar: {
|
||||||
|
open: false,
|
||||||
|
text: "Hello From The Store",
|
||||||
|
color: "info",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
setSnackbar(state, payload) {
|
||||||
|
state.snackbar = payload;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const getters = {
|
||||||
|
getSnackbar: state => state.snackbar,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
state,
|
||||||
|
mutations,
|
||||||
|
getters,
|
||||||
|
};
|
|
@ -1,14 +1,7 @@
|
||||||
import { vueApp } from "../main";
|
|
||||||
import { recipe } from "@/utils/recipe";
|
import { recipe } from "@/utils/recipe";
|
||||||
|
import { store } from "@/store";
|
||||||
|
|
||||||
// TODO: Migrate to Mixins
|
// TODO: Migrate to Mixins
|
||||||
const notifyHelpers = {
|
|
||||||
baseCSS: "notify-base",
|
|
||||||
error: "notify-error-color",
|
|
||||||
warning: "notify-warning-color",
|
|
||||||
success: "notify-success-color",
|
|
||||||
info: "notify-info-color",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const utils = {
|
export const utils = {
|
||||||
recipe: recipe,
|
recipe: recipe,
|
||||||
|
@ -27,27 +20,37 @@ export const utils = {
|
||||||
return `${year}-${month}-${day}`;
|
return `${year}-${month}-${day}`;
|
||||||
},
|
},
|
||||||
notify: {
|
notify: {
|
||||||
show: function(text, type = "info", title = null) {
|
info: function(text, title = null) {
|
||||||
vueApp.flashMessage.show({
|
store.commit("setSnackbar", {
|
||||||
status: type,
|
open: true,
|
||||||
title: title,
|
title: title,
|
||||||
message: text,
|
text: text,
|
||||||
time: 3000,
|
color: "info",
|
||||||
blockClass: `${notifyHelpers.baseCSS} ${notifyHelpers[type]}`,
|
|
||||||
contentClass: `${notifyHelpers.baseCSS} ${notifyHelpers[type]}`,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
info: function(text, title = null) {
|
|
||||||
this.show(text, "info", title);
|
|
||||||
},
|
|
||||||
success: function(text, title = null) {
|
success: function(text, title = null) {
|
||||||
this.show(text, "success", title);
|
store.commit("setSnackbar", {
|
||||||
|
open: true,
|
||||||
|
title: title,
|
||||||
|
text: text,
|
||||||
|
color: "success",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
error: function(text, title = null) {
|
error: function(text, title = null) {
|
||||||
this.show(text, "error", title);
|
store.commit("setSnackbar", {
|
||||||
|
open: true,
|
||||||
|
title: title,
|
||||||
|
text: text,
|
||||||
|
color: "error",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
warning: function(text, title = null) {
|
warning: function(text, title = null) {
|
||||||
this.show(text, "warning", title);
|
store.commit("setSnackbar", {
|
||||||
|
open: true,
|
||||||
|
title: title,
|
||||||
|
text: text,
|
||||||
|
color: "warning",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -90,7 +90,7 @@ def import_database(file_name: str, import_data: ImportJob, session: Session = D
|
||||||
force_import=import_data.force,
|
force_import=import_data.force,
|
||||||
rebase=import_data.rebase,
|
rebase=import_data.rebase,
|
||||||
)
|
)
|
||||||
create_backup_event("Database Restore", f"Restored Database File {file_name}", session)
|
create_backup_event("Database Restore", f"Restore File: {file_name}", session)
|
||||||
return db_import
|
return db_import
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from fastapi import APIRouter, Depends, status, 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.user import GroupBase, GroupInDB, UpdateGroup, UserInDB
|
from mealie.schema.user import GroupBase, GroupInDB, UpdateGroup, UserInDB
|
||||||
|
from mealie.services.events import create_group_event
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/groups", tags=["Groups"])
|
router = APIRouter(prefix="/api/groups", tags=["Groups"])
|
||||||
|
@ -39,6 +40,7 @@ async def create_group(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.groups.create(session, group_data.dict())
|
db.groups.create(session, group_data.dict())
|
||||||
|
create_group_event("Group Created", f"'{group_data.name}' created")
|
||||||
except Exception:
|
except Exception:
|
||||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
@ -68,7 +70,8 @@ async def delete_user_group(
|
||||||
if not group:
|
if not group:
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="GROUP_NOT_FOUND")
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="GROUP_NOT_FOUND")
|
||||||
|
|
||||||
if not group.users == []:
|
if group.users != []:
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="GROUP_WITH_USERS")
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="GROUP_WITH_USERS")
|
||||||
|
|
||||||
|
create_group_event("Group Deleted", f"'{group.name}' Deleted")
|
||||||
db.groups.delete(session, id)
|
db.groups.delete(session, id)
|
||||||
|
|
|
@ -4,6 +4,7 @@ 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.user import GroupInDB, UserInDB
|
from mealie.schema.user import GroupInDB, UserInDB
|
||||||
|
from mealie.services.events import create_group_event
|
||||||
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
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
@ -24,10 +25,11 @@ def get_all_meals(
|
||||||
|
|
||||||
@router.post("/create", status_code=status.HTTP_201_CREATED)
|
@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: UserInDB = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
""" Creates a meal plan database entry """
|
""" Creates a meal plan database entry """
|
||||||
processed_plan = process_meals(session, data)
|
processed_plan = process_meals(session, data)
|
||||||
|
create_group_event("Meal Plan Created", f"Mealplan Created for '{current_user.group}'")
|
||||||
return db.meals.create(session, processed_plan.dict())
|
return db.meals.create(session, processed_plan.dict())
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,23 +38,29 @@ def update_meal_plan(
|
||||||
plan_id: str,
|
plan_id: str,
|
||||||
meal_plan: MealPlanIn,
|
meal_plan: MealPlanIn,
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
current_user=Depends(get_current_user),
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
""" 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())
|
||||||
try:
|
try:
|
||||||
db.meals.update(session, plan_id, processed_plan.dict())
|
db.meals.update(session, plan_id, processed_plan.dict())
|
||||||
|
create_group_event("Meal Plan Updated", f"Mealplan Updated for '{current_user.group}'")
|
||||||
except Exception:
|
except Exception:
|
||||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
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: UserInDB = Depends(get_current_user),
|
||||||
|
):
|
||||||
""" Removes a meal plan from the database """
|
""" Removes a meal plan from the database """
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.meals.delete(session, plan_id)
|
db.meals.delete(session, plan_id)
|
||||||
|
create_group_event("Meal Plan Deleted", f"Mealplan Deleted for '{current_user.group}'")
|
||||||
except Exception:
|
except Exception:
|
||||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
|
@ -26,17 +26,17 @@ async def get_recipe_summary(
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return db.recipes.get_all(session, limit=limit, start=start, override_schema=RecipeSummary)
|
return db.recipes.get_all(session, limit=limit, start=start, order_by="date_updated", override_schema=RecipeSummary)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/recipes/summary/untagged", response_model=list[RecipeSummary])
|
@router.get("/api/recipes/summary/untagged", response_model=list[RecipeSummary])
|
||||||
async def get_untagged_recipes(session: Session = Depends(generate_session)):
|
async def get_untagged_recipes(count: bool = False, session: Session = Depends(generate_session)):
|
||||||
return db.recipes.count_untagged(session, False, override_schema=RecipeSummary)
|
return db.recipes.count_untagged(session, count=count, override_schema=RecipeSummary)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/recipes/summary/uncategorized", response_model=list[RecipeSummary])
|
@router.get("/api/recipes/summary/uncategorized", response_model=list[RecipeSummary])
|
||||||
async def get_uncategorized_recipes(session: Session = Depends(generate_session)):
|
async def get_uncategorized_recipes(count: bool = False, session: Session = Depends(generate_session)):
|
||||||
return db.recipes.count_uncategorized(session, False, override_schema=RecipeSummary)
|
return db.recipes.count_uncategorized(session, count=count, override_schema=RecipeSummary)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/recipes/category")
|
@router.post("/api/recipes/category")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from fastapi import APIRouter, Depends, status
|
from fastapi import APIRouter, Depends, Request, status
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
from fastapi.security import OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
from mealie.core import security
|
from mealie.core import security
|
||||||
|
@ -6,6 +6,7 @@ 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.user import UserInDB
|
from mealie.schema.user import UserInDB
|
||||||
|
from mealie.services.events import create_user_event
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/auth", tags=["Authentication"])
|
router = APIRouter(prefix="/api/auth", tags=["Authentication"])
|
||||||
|
@ -14,6 +15,7 @@ router = APIRouter(prefix="/api/auth", tags=["Authentication"])
|
||||||
@router.post("/token/long")
|
@router.post("/token/long")
|
||||||
@router.post("/token")
|
@router.post("/token")
|
||||||
def get_token(
|
def get_token(
|
||||||
|
request: Request,
|
||||||
data: OAuth2PasswordRequestForm = Depends(),
|
data: OAuth2PasswordRequestForm = Depends(),
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
):
|
):
|
||||||
|
@ -23,6 +25,7 @@ def get_token(
|
||||||
user = authenticate_user(session, email, password)
|
user = authenticate_user(session, email, password)
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
|
create_user_event("Failed Login", f"Username: {email}, Source IP: '{request.client.host}'")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
|
|
@ -9,7 +9,7 @@ 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.user import ChangePassword, UserBase, UserIn, UserInDB, UserOut
|
from mealie.schema.user import ChangePassword, UserBase, UserIn, UserInDB, UserOut
|
||||||
from mealie.services.events import create_sign_up_event
|
from mealie.services.events import create_user_event
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/users", tags=["Users"])
|
router = APIRouter(prefix="/api/users", tags=["Users"])
|
||||||
|
@ -23,7 +23,7 @@ async def create_user(
|
||||||
):
|
):
|
||||||
|
|
||||||
new_user.password = get_password_hash(new_user.password)
|
new_user.password = get_password_hash(new_user.password)
|
||||||
create_sign_up_event("User Created", f"Created by {current_user.full_name}", session=session)
|
create_user_event("User Created", f"Created by {current_user.full_name}", session=session)
|
||||||
return db.users.create(session, new_user.dict())
|
return db.users.create(session, new_user.dict())
|
||||||
|
|
||||||
|
|
||||||
|
@ -150,5 +150,6 @@ async def delete_user(
|
||||||
if current_user.id == id or current_user.admin:
|
if current_user.id == id or current_user.admin:
|
||||||
try:
|
try:
|
||||||
db.users.delete(session, id)
|
db.users.delete(session, id)
|
||||||
|
create_user_event("User Deleted", f"User ID: {id}", session=session)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
|
@ -7,7 +7,7 @@ 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.sign_up import SignUpIn, SignUpOut, SignUpToken
|
from mealie.schema.sign_up import SignUpIn, SignUpOut, SignUpToken
|
||||||
from mealie.schema.user import UserIn, UserInDB
|
from mealie.schema.user import UserIn, UserInDB
|
||||||
from mealie.services.events import create_sign_up_event
|
from mealie.services.events import create_user_event
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/users/sign-ups", tags=["User Signup"])
|
router = APIRouter(prefix="/api/users/sign-ups", tags=["User Signup"])
|
||||||
|
@ -39,7 +39,7 @@ async def create_user_sign_up_key(
|
||||||
"name": key_data.name,
|
"name": key_data.name,
|
||||||
"admin": key_data.admin,
|
"admin": key_data.admin,
|
||||||
}
|
}
|
||||||
create_sign_up_event("Sign-up Token Created", f"Created by {current_user.full_name}", session=session)
|
create_user_event("Sign-up Token Created", f"Created by {current_user.full_name}", session=session)
|
||||||
return db.sign_ups.create(session, sign_up)
|
return db.sign_ups.create(session, sign_up)
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ async def create_user_with_token(
|
||||||
db.users.create(session, new_user.dict())
|
db.users.create(session, new_user.dict())
|
||||||
|
|
||||||
# DeleteToken
|
# DeleteToken
|
||||||
create_sign_up_event("Sign-up Token Used", f"New User {new_user.full_name}", session=session)
|
create_user_event("Sign-up Token Used", f"New User {new_user.full_name}", session=session)
|
||||||
db.sign_ups.delete(session, token)
|
db.sign_ups.delete(session, token)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,8 @@ class EventCategory(str, Enum):
|
||||||
backup = "backup"
|
backup = "backup"
|
||||||
scheduled = "scheduled"
|
scheduled = "scheduled"
|
||||||
migration = "migration"
|
migration = "migration"
|
||||||
sign_up = "signup"
|
group = "group"
|
||||||
|
user = "user"
|
||||||
|
|
||||||
|
|
||||||
class Event(CamelModel):
|
class Event(CamelModel):
|
||||||
|
|
|
@ -35,6 +35,11 @@ def create_migration_event(title, text, session=None):
|
||||||
save_event(title=title, text=text, category=category, session=session)
|
save_event(title=title, text=text, category=category, session=session)
|
||||||
|
|
||||||
|
|
||||||
def create_sign_up_event(title, text, session=None):
|
def create_group_event(title, text, session=None):
|
||||||
category = EventCategory.sign_up
|
category = EventCategory.group
|
||||||
|
save_event(title=title, text=text, category=category, session=session)
|
||||||
|
|
||||||
|
|
||||||
|
def create_user_event(title, text, session=None):
|
||||||
|
category = EventCategory.user
|
||||||
save_event(title=title, text=text, category=category, session=session)
|
save_event(title=title, text=text, category=category, session=session)
|
||||||
|
|
Loading…
Reference in a new issue