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:
Hayden 2021-05-07 14:33:20 -08:00 committed by GitHub
parent 96919319b1
commit 466997febc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1604 additions and 686 deletions

View file

@ -59,7 +59,7 @@
- All images are now converted to .webp for better compression
### General
- New 'Dark' Theme Packages with Mealie
- New 'Dark' Color Theme Packaged with Mealie
- Updated Recipe Card Sections Toolbar
- New Sort Options (They work this time!)
- Alphabetical
@ -82,6 +82,7 @@
- Improved styling for search bar in desktop
- Improved search layout on mobile
- Profile image now shown on all sidebars
- Switched from Flash Messages to Snackbar (Removed dependency
### Behind the Scenes
- Black and Flake8 now run as CI/CD checks

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,6 @@
},
"dependencies": {
"@adapttive/vue-markdown": "^4.0.1",
"@smartweb/vue-flash-message": "^0.6.10",
"axios": "^0.21.1",
"core-js": "^3.9.1",
"fast-levenshtein": "^3.0.0",
@ -31,7 +30,7 @@
"@mdi/font": "^5.9.55",
"@vue/cli-plugin-babel": "^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",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",

View file

@ -6,14 +6,15 @@
<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
>
<GlobalSnackbar />
<router-view></router-view>
</v-main>
<FlashMessage :position="'right bottom'"></FlashMessage>
</v-app>
</template>
<script>
import TheAppBar from "@/components/UI/TheAppBar";
import GlobalSnackbar from "@/components/UI/GlobalSnackbar";
import Vuetify from "./plugins/vuetify";
import { user } from "@/mixins/user";
@ -22,6 +23,7 @@ export default {
components: {
TheAppBar,
GlobalSnackbar,
},
mixins: [user],
@ -71,38 +73,6 @@ export default {
</script>
<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 {
width: 0.25rem;
}

View file

@ -17,6 +17,8 @@ const recipeURLs = {
createAsset: slug => `${prefix}${slug}/assets`,
recipeImage: slug => `${prefix}${slug}/image`,
updateImage: slug => `${prefix}${slug}/image`,
untagged: prefix + "summary/untagged",
uncategorized: prefix + "summary/uncategorized ",
};
export const recipeAPI = {
@ -134,6 +136,16 @@ export const recipeAPI = {
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) {
return `/api/media/recipes/${recipeSlug}/images/original.webp`;
},

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

View file

@ -106,7 +106,7 @@ export default {
this.processing = false;
},
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";
},
},

View file

@ -5,11 +5,9 @@ import store from "./store";
import VueRouter from "vue-router";
import { router } from "./routes";
import i18n from "./i18n";
import FlashMessage from "@smartweb/vue-flash-message";
import "@mdi/font/css/materialdesignicons.css";
import "typeface-roboto/index.css";
Vue.use(FlashMessage);
Vue.config.productionTip = false;
Vue.use(VueRouter);

View file

@ -12,7 +12,7 @@
<template v-slot:after-heading>
<div class="ml-auto text-right">
<h2 class="body-3 grey--text font-weight-light">
{{$t('settings.backup-and-exports')}}
{{ $t("settings.backup-and-exports") }}
</h2>
<h3 class="display-2 font-weight-light text--primary">
@ -23,15 +23,15 @@
<div class="d-flex row py-3 justify-end">
<TheUploadBtn url="/api/backups/upload" @uploaded="getAvailableBackups">
<template v-slot="{ isSelecting, onButtonClick }">
<v-btn :loading="isSelecting" class="mx-2" small :color="color" @click="onButtonClick">
<v-icon left> mdi-cloud-upload </v-icon> {{$t('general.upload')}}
<v-btn :loading="isSelecting" class="mx-2" small color="info" @click="onButtonClick">
<v-icon left> mdi-cloud-upload </v-icon> {{ $t("general.upload") }}
</v-btn>
</template>
</TheUploadBtn>
<BackupDialog :color="color" />
<v-btn :loading="loading" class="mx-2" small :color="color" @click="createBackup">
<v-icon left> mdi-plus </v-icon> {{$t('general.create')}}
<v-btn :loading="loading" class="mx-2" small color="success" @click="createBackup">
<v-icon left> mdi-plus </v-icon> {{ $t("general.create") }}
</v-btn>
</div>
<template v-slot:bottom>

View file

@ -4,7 +4,7 @@
<template v-slot:after-heading>
<div class="ml-auto text-right">
<h2 class="body-3 grey--text font-weight-light">
{{$t('settings.events')}}
{{ $t("settings.events") }}
</h2>
<h3 class="display-2 font-weight-light text--primary">
@ -13,8 +13,8 @@
</div>
</template>
<div class="d-flex row py-3 justify-end">
<v-btn class="mx-2" small :color="color" @click="deleteAll">
<v-icon left> mdi-notification-clear-all </v-icon> {{$t('general.clear')}}
<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-btn>
</div>
<template v-slot:bottom>
@ -69,7 +69,7 @@ export default {
color: "primary",
},
backup: {
icon: "mdi-backup-restore",
icon: "mdi-database",
color: "primary",
},
schedule: {
@ -80,9 +80,13 @@ export default {
icon: "mdi-database-import",
color: "primary",
},
signup: {
user: {
icon: "mdi-account",
color: "primary",
color: "accent",
},
group: {
icon: "mdi-account-group-outline",
color: "accent",
},
},
};

View file

@ -74,7 +74,7 @@
<v-divider></v-divider>
<v-card-actions>
<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-btn>
</v-card-actions>

View file

@ -27,7 +27,7 @@
:top="true"
>
<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>
Change Password
</v-btn>
@ -99,7 +99,6 @@
</template>
<script>
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
import StatCard from "@/components/UI/StatCard";
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";

View file

@ -1,9 +1,10 @@
<template>
<v-card outlined class="mt-n1">
<base-dialog
<BaseDialog
ref="renameDialog"
title-icon="mdi-tag"
:title="renameTarget.title"
:submit-text="$t('general.update')"
modal-width="800"
@submit="renameFromDialog(renameTarget.slug, renameTarget.newName)"
>
@ -32,7 +33,7 @@
:tags="recipe.tags"
/>
</template>
</base-dialog>
</BaseDialog>
<div class="d-flex justify-center align-center pa-2 flex-wrap">
<new-category-tag-dialog ref="newDialog" :tag-dialog="isTags">

View file

@ -13,38 +13,45 @@
</v-btn>
</v-btn-toggle>
<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>
<v-card-text>
<CardSection :sortable="true" title="Unorganized" :recipes="shownRecipes" @sort="assignSorted" />
</v-card-text>
</v-card>
</template>
<script>
import FuseSearchBar from "@/components/UI/Search/FuseSearchBar";
import { api } from "@/api";
import CardSection from "@/components/UI/CardSection";
export default {
components: { FuseSearchBar },
components: {
// FuseSearchBar,
CardSection,
},
data() {
return {
buttonToggle: 0,
allItems: [],
tagRecipes: [],
categoryRecipes: [],
searchString: "",
searchResults: [],
sortedResults: [],
};
},
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() {
return this.$vuetify.breakpoint.name === "xs";
},
@ -60,10 +67,22 @@ export default {
},
},
},
mounted() {
this.refreshUnorganized();
},
methods: {
filterItems(val) {
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>

View file

@ -22,6 +22,12 @@ export default {
currentCategory() {
return this.$route.params.category;
},
currentTag() {
return this.$route.params.tag;
},
TagOrCategory() {
return this.currentCategory || this.currentTag;
},
shownRecipes() {
if (this.sortedResults.length > 0) {
return this.sortedResults;
@ -31,7 +37,7 @@ export default {
},
},
watch: {
async currentCategory() {
async TagOrCategory() {
this.sortedResults = [];
this.getRecipes();
},
@ -42,7 +48,14 @@ export default {
},
methods: {
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.recipes = data.recipes;
},

View file

@ -69,10 +69,7 @@ export default {
methods: {
async buildPage() {
this.page = await api.siteSettings.getPage(this.pageSlug);
},
filterRecipe(slug) {
const storeCategory = this.recipeStore.find(element => element.slug === slug);
return storeCategory ? storeCategory.recipes : [];
this.tab = this.page.categories[0];
},
sortRecipes(sortedRecipes, destKey) {
this.page.categories[destKey].recipes = sortedRecipes;

View file

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

View file

@ -2,15 +2,14 @@ import ViewRecipe from "@/pages/Recipe/ViewRecipe";
import NewRecipe from "@/pages/Recipe/NewRecipe";
import CustomPage from "@/pages/Recipes/CustomPage";
import AllRecipes from "@/pages/Recipes/AllRecipes";
import CategoryPage from "@/pages/Recipes/CategoryPage";
import TagPage from "@/pages/Recipes/TagPage";
import CategoryTagPage from "@/pages/Recipes/CategoryTagPage";
import { api } from "@/api";
export const recipeRoutes = [
// Recipes
{ path: "/recipes/all", component: AllRecipes },
{ path: "/recipes/tag/:tag", component: TagPage },
{ path: "/recipes/category/:category", component: CategoryPage },
{ path: "/recipes/tag/:tag", component: CategoryTagPage },
{ path: "/recipes/category/:category", component: CategoryTagPage },
// Misc
{ path: "/new/", component: NewRecipe },
{ path: "/pages/:customPage", component: CustomPage },

View file

@ -7,6 +7,7 @@ import language from "./modules/language";
import siteSettings from "./modules/siteSettings";
import recipes from "./modules/recipes";
import groups from "./modules/groups";
import snackbar from "./modules/snackbar";
Vue.use(Vuex);
@ -22,6 +23,7 @@ const store = new Vuex.Store({
siteSettings,
groups,
recipes,
snackbar,
},
state: {
// All Recipe Data Store

View file

@ -1,5 +1,6 @@
import { api } from "@/api";
import Vue from "vue";
import { recipe } from "@/utils/recipe";
const state = {
recentRecipes: [],
@ -36,7 +37,6 @@ const mutations = {
const actions = {
async requestRecentRecipes() {
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]));
this.commit("setRecentRecipes", hash);
},
@ -60,7 +60,11 @@ const actions = {
const getters = {
getAllRecipes: state => Object.values(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,
};

View 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,
};

View file

@ -1,14 +1,7 @@
import { vueApp } from "../main";
import { recipe } from "@/utils/recipe";
import { store } from "@/store";
// 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 = {
recipe: recipe,
@ -27,27 +20,37 @@ export const utils = {
return `${year}-${month}-${day}`;
},
notify: {
show: function(text, type = "info", title = null) {
vueApp.flashMessage.show({
status: type,
info: function(text, title = null) {
store.commit("setSnackbar", {
open: true,
title: title,
message: text,
time: 3000,
blockClass: `${notifyHelpers.baseCSS} ${notifyHelpers[type]}`,
contentClass: `${notifyHelpers.baseCSS} ${notifyHelpers[type]}`,
text: text,
color: "info",
});
},
info: function(text, title = null) {
this.show(text, "info", title);
},
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) {
this.show(text, "error", title);
store.commit("setSnackbar", {
open: true,
title: title,
text: text,
color: "error",
});
},
warning: function(text, title = null) {
this.show(text, "warning", title);
store.commit("setSnackbar", {
open: true,
title: title,
text: text,
color: "warning",
});
},
},
};

View file

@ -90,7 +90,7 @@ def import_database(file_name: str, import_data: ImportJob, session: Session = D
force_import=import_data.force,
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

View file

@ -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.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.user import GroupBase, GroupInDB, UpdateGroup, UserInDB
from mealie.services.events import create_group_event
from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/api/groups", tags=["Groups"])
@ -39,6 +40,7 @@ async def create_group(
try:
db.groups.create(session, group_data.dict())
create_group_event("Group Created", f"'{group_data.name}' created")
except Exception:
raise HTTPException(status.HTTP_400_BAD_REQUEST)
@ -68,7 +70,8 @@ async def delete_user_group(
if not group:
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")
create_group_event("Group Deleted", f"'{group.name}' Deleted")
db.groups.delete(session, id)

View file

@ -4,6 +4,7 @@ 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.user import GroupInDB, UserInDB
from mealie.services.events import create_group_event
from mealie.services.image import image
from mealie.services.meal_services import get_todays_meal, process_meals
from sqlalchemy.orm.session import Session
@ -24,10 +25,11 @@ def get_all_meals(
@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)
data: MealPlanIn, session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
):
""" Creates a meal plan database entry """
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())
@ -36,23 +38,29 @@ def update_meal_plan(
plan_id: str,
meal_plan: MealPlanIn,
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 """
processed_plan = process_meals(session, meal_plan)
processed_plan = MealPlanInDB(uid=plan_id, **processed_plan.dict())
try:
db.meals.update(session, plan_id, processed_plan.dict())
create_group_event("Meal Plan Updated", f"Mealplan Updated for '{current_user.group}'")
except Exception:
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)):
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 """
try:
db.meals.delete(session, plan_id)
create_group_event("Meal Plan Deleted", f"Mealplan Deleted for '{current_user.group}'")
except Exception:
raise HTTPException(status.HTTP_400_BAD_REQUEST)

View file

@ -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])
async def get_untagged_recipes(session: Session = Depends(generate_session)):
return db.recipes.count_untagged(session, False, override_schema=RecipeSummary)
async def get_untagged_recipes(count: bool = False, session: Session = Depends(generate_session)):
return db.recipes.count_untagged(session, count=count, override_schema=RecipeSummary)
@router.get("/api/recipes/summary/uncategorized", response_model=list[RecipeSummary])
async def get_uncategorized_recipes(session: Session = Depends(generate_session)):
return db.recipes.count_uncategorized(session, False, override_schema=RecipeSummary)
async def get_uncategorized_recipes(count: bool = False, session: Session = Depends(generate_session)):
return db.recipes.count_uncategorized(session, count=count, override_schema=RecipeSummary)
@router.post("/api/recipes/category")

View file

@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, status
from fastapi import APIRouter, Depends, Request, status
from fastapi.exceptions import HTTPException
from fastapi.security import OAuth2PasswordRequestForm
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.routes.deps import get_current_user
from mealie.schema.user import UserInDB
from mealie.services.events import create_user_event
from sqlalchemy.orm.session import Session
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")
def get_token(
request: Request,
data: OAuth2PasswordRequestForm = Depends(),
session: Session = Depends(generate_session),
):
@ -23,6 +25,7 @@ def get_token(
user = authenticate_user(session, email, password)
if not user:
create_user_event("Failed Login", f"Username: {email}, Source IP: '{request.client.host}'")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
headers={"WWW-Authenticate": "Bearer"},

View file

@ -9,7 +9,7 @@ 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.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
router = APIRouter(prefix="/api/users", tags=["Users"])
@ -23,7 +23,7 @@ async def create_user(
):
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())
@ -150,5 +150,6 @@ async def delete_user(
if current_user.id == id or current_user.admin:
try:
db.users.delete(session, id)
create_user_event("User Deleted", f"User ID: {id}", session=session)
except Exception:
raise HTTPException(status.HTTP_400_BAD_REQUEST)

View file

@ -7,7 +7,7 @@ from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.sign_up import SignUpIn, SignUpOut, SignUpToken
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
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,
"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)
@ -62,7 +62,7 @@ async def create_user_with_token(
db.users.create(session, new_user.dict())
# 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)

View file

@ -12,7 +12,8 @@ class EventCategory(str, Enum):
backup = "backup"
scheduled = "scheduled"
migration = "migration"
sign_up = "signup"
group = "group"
user = "user"
class Event(CamelModel):

View file

@ -35,6 +35,11 @@ def create_migration_event(title, text, session=None):
save_event(title=title, text=text, category=category, session=session)
def create_sign_up_event(title, text, session=None):
category = EventCategory.sign_up
def create_group_event(title, text, session=None):
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)