mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-05-24 01:12:54 -04:00
feat(backend): ✨ migrate site-settings to groups (#673)
* feat(frontend): ✨ add user registration page (WIP) * feat(backend): ✨ add user registration (WIP) * test(backend): ✅ add validator testing for registration schema * feat(backend): ✨ continued work on user sign-up * feat(backend): ✨ add signup flow and user/group settings * test(backend): ✅ user-creation tests and small refactor of existing tests * fix(backend): ✅ fix failing group tests * style: 🎨 fix lint issues Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
parent
e179dcdb10
commit
3c504e7048
@ -8,6 +8,8 @@ const routes = {
|
||||
groupsSelf: `${prefix}/groups/self`,
|
||||
categories: `${prefix}/groups/categories`,
|
||||
|
||||
preferences: `${prefix}/groups/preferences`,
|
||||
|
||||
groupsId: (id: string | number) => `${prefix}/groups/${id}`,
|
||||
};
|
||||
|
||||
@ -21,13 +23,34 @@ export interface CreateGroup {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface UpdatePreferences {
|
||||
privateGroup: boolean;
|
||||
firstDayOfWeek: number;
|
||||
recipePublic: boolean;
|
||||
recipeShowNutrition: boolean;
|
||||
recipeShowAssets: boolean;
|
||||
recipeLandscapeView: boolean;
|
||||
recipeDisableComments: boolean;
|
||||
recipeDisableAmount: boolean;
|
||||
}
|
||||
|
||||
export interface Preferences extends UpdatePreferences {
|
||||
id: number;
|
||||
group_id: number;
|
||||
}
|
||||
|
||||
export interface Group extends CreateGroup {
|
||||
id: number;
|
||||
preferences: Preferences;
|
||||
}
|
||||
|
||||
export class GroupAPI extends BaseCRUDAPI<GroupInDB, CreateGroup> {
|
||||
baseRoute = routes.groups;
|
||||
itemRoute = routes.groupsId;
|
||||
/** Returns the Group Data for the Current User
|
||||
*/
|
||||
async getCurrentUserGroup() {
|
||||
return await this.requests.get(routes.groupsSelf);
|
||||
return await this.requests.get<Group>(routes.groupsSelf);
|
||||
}
|
||||
|
||||
async getCategories() {
|
||||
@ -37,4 +60,12 @@ export class GroupAPI extends BaseCRUDAPI<GroupInDB, CreateGroup> {
|
||||
async setCategories(payload: Category[]) {
|
||||
return await this.requests.put<Category[]>(routes.categories, payload);
|
||||
}
|
||||
|
||||
async getPreferences() {
|
||||
return await this.requests.get<Preferences>(routes.preferences);
|
||||
}
|
||||
|
||||
async setPreferences(payload: UpdatePreferences) {
|
||||
return await this.requests.put<Preferences>(routes.preferences, payload);
|
||||
}
|
||||
}
|
||||
|
25
frontend/api/class-interfaces/user-registration.ts
Normal file
25
frontend/api/class-interfaces/user-registration.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { BaseAPI } from "./_base";
|
||||
|
||||
export interface RegisterPayload {
|
||||
group: string;
|
||||
groupToken: string;
|
||||
email: string;
|
||||
password: string;
|
||||
passwordConfirm: string;
|
||||
advanced: boolean;
|
||||
private: boolean;
|
||||
}
|
||||
|
||||
const prefix = "/api";
|
||||
|
||||
const routes = {
|
||||
register: `${prefix}/users/register`,
|
||||
};
|
||||
|
||||
export class RegisterAPI extends BaseAPI {
|
||||
/** Returns a list of avaiable .zip files for import into Mealie.
|
||||
*/
|
||||
async register(payload: RegisterPayload) {
|
||||
return await this.requests.post<any>(routes.register, payload);
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import { UnitAPI } from "./class-interfaces/recipe-units";
|
||||
import { CookbookAPI } from "./class-interfaces/cookbooks";
|
||||
import { WebhooksAPI } from "./class-interfaces/group-webhooks";
|
||||
import { AdminAboutAPI } from "./class-interfaces/admin-about";
|
||||
import { RegisterAPI } from "./class-interfaces/user-registration";
|
||||
import { ApiRequestInstance } from "~/types/api";
|
||||
|
||||
class AdminAPI {
|
||||
@ -46,6 +47,7 @@ class Api {
|
||||
public units: UnitAPI;
|
||||
public cookbooks: CookbookAPI;
|
||||
public groupWebhooks: WebhooksAPI;
|
||||
public register: RegisterAPI;
|
||||
|
||||
// Utils
|
||||
public upload: UploadFile;
|
||||
@ -67,6 +69,7 @@ class Api {
|
||||
this.groups = new GroupAPI(requests);
|
||||
this.cookbooks = new CookbookAPI(requests);
|
||||
this.groupWebhooks = new WebhooksAPI(requests);
|
||||
this.register = new RegisterAPI(requests);
|
||||
|
||||
// Admin
|
||||
this.events = new EventsAPI(requests);
|
||||
|
@ -61,13 +61,7 @@
|
||||
</v-fade-transition>
|
||||
</v-card-title>
|
||||
<v-card-text v-if="edit">
|
||||
<v-textarea
|
||||
:key="generateKey('instructions', index)"
|
||||
v-model="value[index]['text']"
|
||||
auto-grow
|
||||
dense
|
||||
rows="4"
|
||||
>
|
||||
<v-textarea :key="'instructions' + index" v-model="value[index]['text']" auto-grow dense rows="4">
|
||||
</v-textarea>
|
||||
</v-card-text>
|
||||
<v-expand-transition>
|
||||
|
@ -134,9 +134,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from "@nuxtjs/composition-api";
|
||||
import { validators } from "@/composables/use-validators";
|
||||
import { fieldTypes } from "@/composables/forms";
|
||||
import { ref } from "@nuxtjs/composition-api";
|
||||
|
||||
const BLUR_EVENT = "blur";
|
||||
|
||||
|
@ -138,9 +138,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from "@nuxtjs/composition-api";
|
||||
import { validators } from "@/composables/use-validators";
|
||||
import { fieldTypes } from "@/composables/forms";
|
||||
import { ref } from "@nuxtjs/composition-api";
|
||||
|
||||
const BLUR_EVENT = "blur";
|
||||
|
||||
|
@ -3,7 +3,38 @@ import { useAsyncKey } from "./use-utils";
|
||||
import { useApiSingleton } from "~/composables/use-api";
|
||||
import { CreateGroup } from "~/api/class-interfaces/groups";
|
||||
|
||||
export const useGroup = function () {
|
||||
export const useGroupSelf = function () {
|
||||
const api = useApiSingleton();
|
||||
|
||||
const actions = {
|
||||
get() {
|
||||
const group = useAsync(async () => {
|
||||
const { data } = await api.groups.getCurrentUserGroup();
|
||||
|
||||
return data;
|
||||
}, useAsyncKey());
|
||||
|
||||
return group;
|
||||
},
|
||||
async updatePreferences() {
|
||||
if (!group.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { data } = await api.groups.setPreferences(group.value.preferences);
|
||||
|
||||
if (data) {
|
||||
group.value.preferences = data;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const group = actions.get();
|
||||
|
||||
return { actions, group };
|
||||
};
|
||||
|
||||
export const useGroupCategories = function () {
|
||||
const api = useApiSingleton();
|
||||
|
||||
const actions = {
|
||||
@ -61,7 +92,6 @@ export const useGroups = function () {
|
||||
}
|
||||
|
||||
async function createGroup(payload: CreateGroup) {
|
||||
console.log(payload);
|
||||
loading.value = true;
|
||||
const { data } = await api.groups.createOne(payload);
|
||||
|
||||
|
@ -1,13 +1,10 @@
|
||||
<template>
|
||||
<v-app dark>
|
||||
<!-- <TheSnackbar /> -->
|
||||
|
||||
<AppSidebar
|
||||
v-model="sidebar"
|
||||
absolute
|
||||
:top-link="topLinks"
|
||||
:secondary-links="$auth.user.admin ? adminLinks : null"
|
||||
:bottom-links="$auth.user.admin ? bottomLinks : null"
|
||||
:bottom-links="bottomLinks"
|
||||
:user="{ data: true }"
|
||||
:secondary-header="$t('user.admin')"
|
||||
@input="sidebar = !sidebar"
|
||||
@ -30,7 +27,7 @@
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
||||
import AppHeader from "@/components/Layout/AppHeader.vue";
|
||||
import AppSidebar from "@/components/Layout/AppSidebar.vue";
|
||||
import TheSnackbar from "~/components/Layout/TheSnackbar.vue";
|
||||
@ -40,103 +37,110 @@ export default defineComponent({
|
||||
middleware: "auth",
|
||||
auth: true,
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
data() {
|
||||
// @ts-ignore - $globals not found in type definition
|
||||
const { $globals, i18n } = useContext();
|
||||
|
||||
const sidebar = ref(null);
|
||||
|
||||
const topLinks = [
|
||||
{
|
||||
icon: $globals.icons.viewDashboard,
|
||||
to: "/admin/dashboard",
|
||||
title: i18n.t("sidebar.dashboard"),
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.cog,
|
||||
to: "/admin/site-settings",
|
||||
title: i18n.t("sidebar.site-settings"),
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.tools,
|
||||
to: "/admin/toolbox",
|
||||
title: i18n.t("sidebar.toolbox"),
|
||||
children: [
|
||||
{
|
||||
icon: $globals.icons.bellAlert,
|
||||
to: "/admin/toolbox/notifications",
|
||||
title: i18n.t("events.notification"),
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.foods,
|
||||
to: "/admin/toolbox/foods",
|
||||
title: "Manage Foods",
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.units,
|
||||
to: "/admin/toolbox/units",
|
||||
title: "Manage Units",
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.tags,
|
||||
to: "/admin/toolbox/categories",
|
||||
title: i18n.t("sidebar.tags"),
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.tags,
|
||||
to: "/admin/toolbox/tags",
|
||||
title: i18n.t("sidebar.categories"),
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.broom,
|
||||
to: "/admin/toolbox/organize",
|
||||
title: i18n.t("settings.organize"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.group,
|
||||
to: "/admin/manage-users",
|
||||
title: i18n.t("sidebar.manage-users"),
|
||||
children: [
|
||||
{
|
||||
icon: $globals.icons.user,
|
||||
to: "/admin/manage-users/all-users",
|
||||
title: i18n.t("user.users"),
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.group,
|
||||
to: "/admin/manage-users/all-groups",
|
||||
title: i18n.t("group.groups"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.import,
|
||||
to: "/admin/migrations",
|
||||
title: i18n.t("sidebar.migrations"),
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.database,
|
||||
to: "/admin/backups",
|
||||
title: i18n.t("sidebar.backups"),
|
||||
},
|
||||
];
|
||||
|
||||
const bottomLinks = [
|
||||
{
|
||||
icon: $globals.icons.heart,
|
||||
title: i18n.t("about.support"),
|
||||
href: "https://github.com/sponsors/hay-kot",
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.information,
|
||||
title: i18n.t("about.about"),
|
||||
to: "/admin/about",
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
sidebar: null,
|
||||
topLinks: [
|
||||
{
|
||||
icon: this.$globals.icons.viewDashboard,
|
||||
to: "/admin/dashboard",
|
||||
title: this.$t("sidebar.dashboard"),
|
||||
},
|
||||
{
|
||||
icon: this.$globals.icons.cog,
|
||||
to: "/admin/site-settings",
|
||||
title: this.$t("sidebar.site-settings"),
|
||||
},
|
||||
{
|
||||
icon: this.$globals.icons.tools,
|
||||
to: "/admin/toolbox",
|
||||
title: this.$t("sidebar.toolbox"),
|
||||
children: [
|
||||
{
|
||||
icon: this.$globals.icons.bellAlert,
|
||||
to: "/admin/toolbox/notifications",
|
||||
title: this.$t("events.notification"),
|
||||
},
|
||||
{
|
||||
icon: this.$globals.icons.foods,
|
||||
to: "/admin/toolbox/foods",
|
||||
title: "Manage Foods",
|
||||
},
|
||||
{
|
||||
icon: this.$globals.icons.units,
|
||||
to: "/admin/toolbox/units",
|
||||
title: "Manage Units",
|
||||
},
|
||||
{
|
||||
icon: this.$globals.icons.tags,
|
||||
to: "/admin/toolbox/categories",
|
||||
title: this.$t("sidebar.tags"),
|
||||
},
|
||||
{
|
||||
icon: this.$globals.icons.tags,
|
||||
to: "/admin/toolbox/tags",
|
||||
title: this.$t("sidebar.categories"),
|
||||
},
|
||||
{
|
||||
icon: this.$globals.icons.broom,
|
||||
to: "/admin/toolbox/organize",
|
||||
title: this.$t("settings.organize"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: this.$globals.icons.group,
|
||||
to: "/admin/manage-users",
|
||||
title: this.$t("sidebar.manage-users"),
|
||||
children: [
|
||||
{
|
||||
icon: this.$globals.icons.user,
|
||||
to: "/admin/manage-users/all-users",
|
||||
title: this.$t("user.users"),
|
||||
},
|
||||
{
|
||||
icon: this.$globals.icons.group,
|
||||
to: "/admin/manage-users/all-groups",
|
||||
title: this.$t("group.groups"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: this.$globals.icons.import,
|
||||
to: "/admin/migrations",
|
||||
title: this.$t("sidebar.migrations"),
|
||||
},
|
||||
{
|
||||
icon: this.$globals.icons.database,
|
||||
to: "/admin/backups",
|
||||
title: this.$t("sidebar.backups"),
|
||||
},
|
||||
],
|
||||
bottomLinks: [
|
||||
{
|
||||
icon: this.$globals.icons.heart,
|
||||
title: this.$t("about.support"),
|
||||
href: "https://github.com/sponsors/hay-kot",
|
||||
},
|
||||
{
|
||||
icon: this.$globals.icons.information,
|
||||
title: this.$t("about.about"),
|
||||
to: "/admin/about",
|
||||
},
|
||||
],
|
||||
sidebar,
|
||||
topLinks,
|
||||
bottomLinks,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>+
|
||||
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<v-app dark>
|
||||
<!-- <TheSnackbar /> -->
|
||||
<TheSnackbar />
|
||||
|
||||
<AppHeader :menu="false"> </AppHeader>
|
||||
<v-main>
|
||||
@ -17,9 +17,10 @@
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import AppFooter from "@/components/Layout/AppFooter.vue";
|
||||
import AppHeader from "@/components/Layout/AppHeader.vue";
|
||||
import TheSnackbar from "~/components/Layout/TheSnackbar.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: { AppHeader, AppFooter },
|
||||
components: { AppHeader, AppFooter, TheSnackbar },
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
|
@ -8,7 +8,7 @@
|
||||
:top-link="topLinks"
|
||||
secondary-header="Cookbooks"
|
||||
:secondary-links="cookbookLinks || []"
|
||||
:bottom-links="$auth.user.admin ? bottomLink : []"
|
||||
:bottom-links="isAdmin ? bottomLink : []"
|
||||
@input="sidebar = !sidebar"
|
||||
/>
|
||||
|
||||
@ -37,11 +37,13 @@ import { useCookbooks } from "~/composables/use-group-cookbooks";
|
||||
export default defineComponent({
|
||||
components: { AppHeader, AppSidebar, AppFloatingButton },
|
||||
// @ts-ignore
|
||||
// middleware: process.env.GLOBAL_MIDDLEWARE,
|
||||
middleware: "auth",
|
||||
setup() {
|
||||
const { cookbooks } = useCookbooks();
|
||||
// @ts-ignore
|
||||
const { $globals } = useContext();
|
||||
const { $globals, $auth } = useContext();
|
||||
|
||||
const isAdmin = computed(() => $auth.user?.admin);
|
||||
|
||||
const cookbookLinks = computed(() => {
|
||||
if (!cookbooks.value) return [];
|
||||
@ -53,7 +55,7 @@ export default defineComponent({
|
||||
};
|
||||
});
|
||||
});
|
||||
return { cookbookLinks };
|
||||
return { cookbookLinks, isAdmin };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -56,7 +56,7 @@ export default {
|
||||
// https://go.nuxtjs.dev/pwa
|
||||
"@nuxtjs/pwa",
|
||||
// https://i18n.nuxtjs.org/setup
|
||||
"nuxt-i18n",
|
||||
"@nuxtjs/i18n",
|
||||
// https://auth.nuxtjs.org/guide/setup
|
||||
"@nuxtjs/auth-next",
|
||||
// https://github.com/nuxt-community/proxy-module
|
||||
@ -81,8 +81,8 @@ export default {
|
||||
|
||||
auth: {
|
||||
redirect: {
|
||||
login: "/user/login",
|
||||
logout: "/",
|
||||
login: "/login",
|
||||
logout: "/login",
|
||||
callback: "/login",
|
||||
home: "/",
|
||||
},
|
||||
|
@ -18,6 +18,7 @@
|
||||
"@mdi/js": "^5.9.55",
|
||||
"@nuxtjs/auth-next": "5.0.0-1624817847.21691f1",
|
||||
"@nuxtjs/axios": "^5.13.6",
|
||||
"@nuxtjs/i18n": "^7.0.3",
|
||||
"@nuxtjs/proxy": "^2.1.0",
|
||||
"@nuxtjs/pwa": "^3.3.5",
|
||||
"@vue/composition-api": "^1.0.5",
|
||||
@ -25,7 +26,6 @@
|
||||
"core-js": "^3.15.1",
|
||||
"fuse.js": "^6.4.6",
|
||||
"nuxt": "^2.15.7",
|
||||
"nuxt-i18n": "^6.28.0",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuetify": "^2.5.5"
|
||||
},
|
||||
@ -33,7 +33,7 @@
|
||||
"@babel/eslint-parser": "^7.14.7",
|
||||
"@nuxt/types": "^2.15.7",
|
||||
"@nuxt/typescript-build": "^2.1.0",
|
||||
"@nuxtjs/composition-api": "^0.26.0",
|
||||
"@nuxtjs/composition-api": "^0.28.0",
|
||||
"@nuxtjs/eslint-config-typescript": "^6.0.1",
|
||||
"@nuxtjs/eslint-module": "^3.0.2",
|
||||
"@nuxtjs/vuetify": "^1.12.1",
|
||||
@ -50,4 +50,4 @@
|
||||
"resolutions": {
|
||||
"vite": "2.3.8"
|
||||
}
|
||||
}
|
||||
}
|
@ -95,8 +95,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import AdminBackupImportOptions from "@/components/Domain/Admin/AdminBackupImportOptions.vue";
|
||||
import { defineComponent, reactive, toRefs, useContext, ref } from "@nuxtjs/composition-api";
|
||||
import AdminBackupImportOptions from "@/components/Domain/Admin/AdminBackupImportOptions.vue";
|
||||
import { useBackups } from "~/composables/use-backups";
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -23,8 +23,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue";
|
||||
import { defineComponent, useRoute, ref } from "@nuxtjs/composition-api";
|
||||
import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue";
|
||||
import { useCookbook } from "~/composables/use-group-cookbooks";
|
||||
export default defineComponent({
|
||||
components: { RecipeCardSection },
|
||||
|
@ -178,10 +178,9 @@
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-btn v-if="$config.ALLOW_SIGNUP" class="mx-auto" text to="/user/sign-up"> Sign Up </v-btn>
|
||||
<v-btn v-if="$config.ALLOW_SIGNUP" class="mx-auto" text to="/register"> Register </v-btn>
|
||||
<v-btn v-else class="mx-auto" text disabled> Invite Only </v-btn>
|
||||
</v-card>
|
||||
<!-- <v-col class="fill-height"> </v-col> -->
|
||||
</v-container>
|
||||
</template>
|
||||
|
170
frontend/pages/register.vue
Normal file
170
frontend/pages/register.vue
Normal file
@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<v-container fill-height fluid class="d-flex justify-center align-start narrow-container">
|
||||
<v-card color="background d-flex flex-column align-center" flat width="700px">
|
||||
<v-card-title class="headline"> User Registration </v-card-title>
|
||||
<v-card-text>
|
||||
<v-form ref="domRegisterForm" @submit.prevent="register()">
|
||||
<ToggleState>
|
||||
<template #activator="{ toggle }">
|
||||
<div class="d-flex justify-center my-2">
|
||||
<v-btn-toggle tile mandatory group color="primary">
|
||||
<v-btn small @click="toggle(false)"> Create a Group </v-btn>
|
||||
<v-btn small @click="toggle(true)"> Join a Group </v-btn>
|
||||
</v-btn-toggle>
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ state }">
|
||||
<v-text-field
|
||||
v-if="!state"
|
||||
v-model="form.group"
|
||||
filled
|
||||
rounded
|
||||
autofocus
|
||||
validate-on-blur
|
||||
class="rounded-lg"
|
||||
:prepend-icon="$globals.icons.group"
|
||||
:rules="[tokenOrGroup]"
|
||||
label="New Group Name"
|
||||
/>
|
||||
<v-text-field
|
||||
v-else
|
||||
v-model="form.groupToken"
|
||||
filled
|
||||
rounded
|
||||
validate-on-blur
|
||||
:rules="[tokenOrGroup]"
|
||||
class="rounded-lg"
|
||||
:prepend-icon="$globals.icons.group"
|
||||
label="Group Token"
|
||||
/>
|
||||
</template>
|
||||
</ToggleState>
|
||||
<v-text-field
|
||||
v-model="form.email"
|
||||
filled
|
||||
rounded
|
||||
class="rounded-lg"
|
||||
validate-on-blur
|
||||
:prepend-icon="$globals.icons.email"
|
||||
label="Email"
|
||||
:rules="[validators.required, validators.email]"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="form.username"
|
||||
filled
|
||||
rounded
|
||||
class="rounded-lg"
|
||||
:prepend-icon="$globals.icons.user"
|
||||
label="Username"
|
||||
:rules="[validators.required]"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="form.password"
|
||||
filled
|
||||
rounded
|
||||
class="rounded-lg"
|
||||
:prepend-icon="$globals.icons.lock"
|
||||
name="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
:rules="[validators.required]"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="form.passwordConfirm"
|
||||
filled
|
||||
rounded
|
||||
validate-on-blur
|
||||
class="rounded-lg"
|
||||
:prepend-icon="$globals.icons.lock"
|
||||
name="password"
|
||||
label="Confirm Password"
|
||||
type="password"
|
||||
:rules="[validators.required, passwordMatch]"
|
||||
/>
|
||||
<div class="mt-n4 px-8">
|
||||
<v-checkbox v-model="form.private" label="Keep My Recipes Private"></v-checkbox>
|
||||
<p class="text-caption mt-n4">
|
||||
Sets your group and all recipes defaults to private. You can always change this later.
|
||||
</p>
|
||||
<v-checkbox v-model="form.advanced" label="Enable Advanced Content"></v-checkbox>
|
||||
<p class="text-caption mt-n4">
|
||||
Enables advanced features like Recipe Scaling, API keys, Webhooks, and Data Management. Don't worry, you
|
||||
can always change this later
|
||||
</p>
|
||||
</div>
|
||||
<div class="d-flex flex-column justify-center">
|
||||
<v-btn :loading="loggingIn" color="primary" type="submit" large rounded class="rounded-xl" block>
|
||||
Register
|
||||
</v-btn>
|
||||
<v-btn class="mx-auto my-2" text to="/login"> Login </v-btn>
|
||||
</div>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, toRefs, ref, useRouter } from "@nuxtjs/composition-api";
|
||||
import { validators } from "@/composables/use-validators";
|
||||
import { useApiSingleton } from "~/composables/use-api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
|
||||
export default defineComponent({
|
||||
layout: "basic",
|
||||
setup() {
|
||||
const api = useApiSingleton();
|
||||
const state = reactive({
|
||||
loggingIn: false,
|
||||
success: false,
|
||||
});
|
||||
const allowSignup = computed(() => process.env.AllOW_SIGNUP);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// @ts-ignore
|
||||
const domRegisterForm = ref<VForm>(null);
|
||||
|
||||
const form = reactive({
|
||||
group: "",
|
||||
groupToken: "",
|
||||
email: "",
|
||||
username: "",
|
||||
password: "",
|
||||
passwordConfirm: "",
|
||||
advanced: false,
|
||||
private: false,
|
||||
});
|
||||
|
||||
const passwordMatch = () => form.password === form.passwordConfirm || "Passwords do not match";
|
||||
const tokenOrGroup = () => form.group !== "" || form.groupToken !== "" || "Group name or token must be given";
|
||||
|
||||
async function register() {
|
||||
if (!domRegisterForm.value?.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { data, response } = await api.register.register(form);
|
||||
|
||||
if (response?.status === 201) {
|
||||
state.success = true;
|
||||
alert.success("Registration Success");
|
||||
router.push("/user/login");
|
||||
}
|
||||
|
||||
console.log(data, response);
|
||||
}
|
||||
|
||||
return {
|
||||
domRegisterForm,
|
||||
validators,
|
||||
allowSignup,
|
||||
form,
|
||||
...toRefs(state),
|
||||
passwordMatch,
|
||||
tokenOrGroup,
|
||||
register,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
@ -50,8 +50,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import { useCookbooks } from "@/composables/use-group-cookbooks";
|
||||
import draggable from "vuedraggable";
|
||||
import { useCookbooks } from "@/composables/use-group-cookbooks";
|
||||
|
||||
export default defineComponent({
|
||||
components: { draggable },
|
||||
|
@ -1,39 +1,139 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<BasePageTitle divider>
|
||||
<v-container class="narrow-container">
|
||||
<BasePageTitle class="mb-5">
|
||||
<template #header>
|
||||
<v-img max-height="100" max-width="100" :src="require('~/static/svgs/manage-group-settings.svg')"></v-img>
|
||||
</template>
|
||||
<template #title> Group Settings </template>
|
||||
These items are shared within your group. Editing one of them will change it for the whole group!
|
||||
</BasePageTitle>
|
||||
<v-card tag="section" outlined>
|
||||
<v-card-text>
|
||||
<BaseCardSectionTitle title="Mealplan Categories">
|
||||
Set the categories below for the ones that you want to be included in your mealplan random generation.
|
||||
<div class="mt-2">
|
||||
<BaseButton save @click="actions.updateAll()" />
|
||||
</div>
|
||||
</BaseCardSectionTitle>
|
||||
<DomainRecipeCategoryTagSelector v-if="categories" v-model="categories" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<section>
|
||||
<BaseCardSectionTitle title="Mealplan Categories">
|
||||
Set the categories below for the ones that you want to be included in your mealplan random generation.
|
||||
</BaseCardSectionTitle>
|
||||
<DomainRecipeCategoryTagSelector v-if="categories" v-model="categories" />
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<BaseButton save @click="actions.updateAll()" />
|
||||
</v-card-actions>
|
||||
</section>
|
||||
|
||||
<section v-if="group">
|
||||
<BaseCardSectionTitle class="mt-10" title="Group Preferences"></BaseCardSectionTitle>
|
||||
<v-checkbox
|
||||
v-model="group.preferences.privateGroup"
|
||||
class="mt-n4"
|
||||
label="Private Group"
|
||||
@change="groupActions.updatePreferences()"
|
||||
></v-checkbox>
|
||||
<v-select
|
||||
v-model="group.preferences.firstDayOfWeek"
|
||||
:prepend-icon="$globals.icons.calendarWeekBegin"
|
||||
:items="allDays"
|
||||
item-text="name"
|
||||
item-value="value"
|
||||
:label="$t('settings.first-day-of-week')"
|
||||
@change="groupActions.updatePreferences()"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section v-if="group">
|
||||
<BaseCardSectionTitle class="mt-10" title="Default Recipe Preferences">
|
||||
These are the default settings when a new recipe is created in your group. These can be changed for indivdual
|
||||
recipes in the recipe settings menu.
|
||||
</BaseCardSectionTitle>
|
||||
|
||||
<v-checkbox
|
||||
v-model="group.preferences.recipePublic"
|
||||
class="mt-n4"
|
||||
label="Allow users outside of your group to see your recipes"
|
||||
@change="groupActions.updatePreferences()"
|
||||
></v-checkbox>
|
||||
<v-checkbox
|
||||
v-model="group.preferences.recipeShowNutrition"
|
||||
class="mt-n4"
|
||||
label="Show nutrition information"
|
||||
@change="groupActions.updatePreferences()"
|
||||
></v-checkbox>
|
||||
<v-checkbox
|
||||
v-model="group.preferences.recipeShowAssets"
|
||||
class="mt-n4"
|
||||
label="Show recipe assets"
|
||||
@change="groupActions.updatePreferences()"
|
||||
></v-checkbox>
|
||||
<v-checkbox
|
||||
v-model="group.preferences.recipeLandscapeView"
|
||||
class="mt-n4"
|
||||
label="Default to landscape view"
|
||||
@change="groupActions.updatePreferences()"
|
||||
></v-checkbox>
|
||||
<v-checkbox
|
||||
v-model="group.preferences.recipeDisableComments"
|
||||
class="mt-n4"
|
||||
label="Allow recipe comments from users in your group"
|
||||
@change="groupActions.updatePreferences()"
|
||||
></v-checkbox>
|
||||
<v-checkbox
|
||||
v-model="group.preferences.recipeDisableAmount"
|
||||
class="mt-n4"
|
||||
label="Enable organizing recipe ingredients by units and food"
|
||||
@change="groupActions.updatePreferences()"
|
||||
></v-checkbox>
|
||||
</section>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import { useGroup } from "~/composables/use-groups";
|
||||
import { defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||
import { useGroupCategories, useGroupSelf } from "~/composables/use-groups";
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const { categories, actions } = useGroup();
|
||||
const { categories, actions } = useGroupCategories();
|
||||
const { group, actions: groupActions } = useGroupSelf();
|
||||
|
||||
const { i18n } = useContext();
|
||||
|
||||
const allDays = [
|
||||
{
|
||||
name: i18n.t("general.sunday"),
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: i18n.t("general.monday"),
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
name: i18n.t("general.tuesday"),
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
name: i18n.t("general.wednesday"),
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
name: i18n.t("general.thursday"),
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
name: i18n.t("general.friday"),
|
||||
value: 5,
|
||||
},
|
||||
{
|
||||
name: i18n.t("general.saturday"),
|
||||
value: 6,
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
categories,
|
||||
actions,
|
||||
group,
|
||||
groupActions,
|
||||
allDays,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
@ -82,6 +82,18 @@
|
||||
</template>
|
||||
</ToggleState>
|
||||
</section>
|
||||
<section>
|
||||
<BaseCardSectionTitle class="mt-10" title="Preferences"> </BaseCardSectionTitle>
|
||||
<v-checkbox
|
||||
v-model="userCopy.advanced"
|
||||
class="mt-n4"
|
||||
label="Show advanced features (API Keys, Webhooks, and Data Management)"
|
||||
@change="updateUser"
|
||||
></v-checkbox>
|
||||
<div class="d-flex justify-center mt-5">
|
||||
<v-btn outlined class="rounded-xl" to="/user/group"> Looking for Privacy Settings? </v-btn>
|
||||
</div>
|
||||
</section>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="6">
|
||||
<UserProfileLinkCard
|
||||
v-if="user.advanced"
|
||||
:link="{ text: 'Manage Your API Tokens', to: '/user/profile/api-tokens' }"
|
||||
:image="require('~/static/svgs/manage-api-tokens.svg')"
|
||||
>
|
||||
@ -63,6 +64,7 @@
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="6">
|
||||
<UserProfileLinkCard
|
||||
v-if="user.advanced"
|
||||
:link="{ text: 'Manage Webhooks', to: '/user/group/webhooks' }"
|
||||
:image="require('~/static/svgs/manage-webhooks.svg')"
|
||||
>
|
||||
|
@ -16,7 +16,7 @@
|
||||
"~/*": ["./*"],
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"types": ["@nuxt/types", "@nuxtjs/axios", "@types/node", "nuxt-i18n", "@nuxtjs/auth-next"]
|
||||
"types": ["@nuxt/types", "@nuxtjs/axios", "@types/node", "@nuxtjs/i18n", "@nuxtjs/auth-next"]
|
||||
},
|
||||
"exclude": ["node_modules", ".nuxt", "dist"]
|
||||
}
|
||||
|
17
frontend/types/vue.d.ts
vendored
17
frontend/types/vue.d.ts
vendored
@ -1,14 +1,17 @@
|
||||
import Vue from "vue";
|
||||
import "@nuxt/types";
|
||||
|
||||
declare module "vue/types/vue" {
|
||||
|
||||
interface Vue {
|
||||
$globals: any;
|
||||
}
|
||||
interface Vue {
|
||||
$globals: any;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "vue/types/options" {
|
||||
interface ComponentOptions<V extends Vue> {
|
||||
$globals?: any;
|
||||
}
|
||||
interface ComponentOptions<V extends Vue> {
|
||||
$globals?: any;
|
||||
}
|
||||
interface ComponentOptions<V extends UseContextReturn> {
|
||||
$globals?: any;
|
||||
}
|
||||
}
|
||||
|
1267
frontend/yarn.lock
1267
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,7 @@ from mealie.db.data_access_layer.group_access_model import GroupDataAccessModel
|
||||
from mealie.db.models.event import Event, EventNotification
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.group.cookbook import CookBook
|
||||
from mealie.db.models.group.preferences import GroupPreferencesModel
|
||||
from mealie.db.models.group.shopping_list import ShoppingList
|
||||
from mealie.db.models.group.webhooks import GroupWebhooksModel
|
||||
from mealie.db.models.mealplan import MealPlan
|
||||
@ -20,6 +21,7 @@ from mealie.schema.admin import SiteSettings as SiteSettingsSchema
|
||||
from mealie.schema.cookbook import ReadCookBook
|
||||
from mealie.schema.events import Event as EventSchema
|
||||
from mealie.schema.events import EventNotificationIn
|
||||
from mealie.schema.group.group_preferences import ReadGroupPreferences
|
||||
from mealie.schema.group.webhook import ReadWebhook
|
||||
from mealie.schema.meal_plan import MealPlanOut, ShoppingListOut
|
||||
from mealie.schema.recipe import (
|
||||
@ -89,3 +91,4 @@ class DatabaseAccessLayer:
|
||||
self.webhooks = BaseAccessModel(pk_id, GroupWebhooksModel, ReadWebhook)
|
||||
self.shopping_lists = BaseAccessModel(pk_id, ShoppingList, ShoppingListOut)
|
||||
self.cookbooks = BaseAccessModel(pk_id, CookBook, ReadCookBook)
|
||||
self.group_preferences = BaseAccessModel("group_id", GroupPreferencesModel, ReadGroupPreferences)
|
||||
|
@ -24,13 +24,11 @@ def default_recipe_unit_init(db: DatabaseAccessLayer, session: Session) -> None:
|
||||
for unit in get_default_units():
|
||||
try:
|
||||
db.ingredient_units.create(session, unit)
|
||||
print("Ingredient Unit Created")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
for food in get_default_foods():
|
||||
try:
|
||||
db.ingredient_foods.create(session, food)
|
||||
print("Ingredient Food Created")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
@ -8,7 +8,9 @@ from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session, engine
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
from mealie.schema.admin import SiteSettings
|
||||
from mealie.schema.user.user import GroupBase
|
||||
from mealie.services.events import create_general_event
|
||||
from mealie.services.group_services.group_mixins import create_new_group
|
||||
|
||||
logger = root_logger.get_logger("init_db")
|
||||
|
||||
@ -38,9 +40,8 @@ def default_settings_init(session: Session):
|
||||
|
||||
|
||||
def default_group_init(session: Session):
|
||||
default_group = {"name": settings.DEFAULT_GROUP}
|
||||
logger.info("Generating Default Group")
|
||||
db.groups.create(session, default_group)
|
||||
create_new_group(session, GroupBase(name=settings.DEFAULT_GROUP))
|
||||
|
||||
|
||||
def default_user_init(session: Session):
|
||||
|
@ -82,8 +82,6 @@ def auto_init(exclude: Union[set, list] = None): # sourcery no-metrics
|
||||
except Exception:
|
||||
get_attr = "id"
|
||||
|
||||
print(get_attr)
|
||||
|
||||
if relation_dir == ONETOMANY.name and use_list:
|
||||
instances = handle_one_to_many_list(get_attr, relation_cls, val)
|
||||
setattr(self, key, instances)
|
||||
|
@ -9,6 +9,7 @@ from .._model_utils import auto_init
|
||||
from ..group.webhooks import GroupWebhooksModel
|
||||
from ..recipe.category import Category, group2categories
|
||||
from .cookbook import CookBook
|
||||
from .preferences import GroupPreferencesModel
|
||||
|
||||
|
||||
class Group(SqlAlchemyBase, BaseMixins):
|
||||
@ -16,7 +17,15 @@ class Group(SqlAlchemyBase, BaseMixins):
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
name = sa.Column(sa.String, index=True, nullable=False, unique=True)
|
||||
users = orm.relationship("User", back_populates="group")
|
||||
categories = orm.relationship(Category, secondary=group2categories, single_parent=True)
|
||||
categories = orm.relationship(Category, secondary=group2categories, single_parent=True, uselist=True)
|
||||
|
||||
preferences = orm.relationship(
|
||||
GroupPreferencesModel,
|
||||
back_populates="group",
|
||||
uselist=False,
|
||||
single_parent=True,
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
# CRUD From Others
|
||||
mealplans = orm.relationship("MealPlan", back_populates="group", single_parent=True, order_by="MealPlan.start_date")
|
||||
@ -24,7 +33,7 @@ class Group(SqlAlchemyBase, BaseMixins):
|
||||
cookbooks = orm.relationship(CookBook, back_populates="group", single_parent=True)
|
||||
shopping_lists = orm.relationship("ShoppingList", back_populates="group", single_parent=True)
|
||||
|
||||
@auto_init({"users", "webhooks", "shopping_lists", "cookbooks"})
|
||||
@auto_init({"users", "webhooks", "shopping_lists", "cookbooks", "preferences"})
|
||||
def __init__(self, **_) -> None:
|
||||
pass
|
||||
|
||||
|
26
mealie/db/models/group/preferences.py
Normal file
26
mealie/db/models/group/preferences.py
Normal file
@ -0,0 +1,26 @@
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
|
||||
from .._model_base import BaseMixins, SqlAlchemyBase
|
||||
from .._model_utils import auto_init
|
||||
|
||||
|
||||
class GroupPreferencesModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "group_preferences"
|
||||
group_id = sa.Column(sa.Integer, sa.ForeignKey("groups.id"))
|
||||
group = orm.relationship("Group", back_populates="preferences")
|
||||
|
||||
private_group: bool = sa.Column(sa.Boolean, default=True)
|
||||
first_day_of_week = sa.Column(sa.Integer, default=0)
|
||||
|
||||
# Recipe Defaults
|
||||
recipe_public: bool = sa.Column(sa.Boolean, default=True)
|
||||
recipe_show_nutrition: bool = sa.Column(sa.Boolean, default=False)
|
||||
recipe_show_assets: bool = sa.Column(sa.Boolean, default=False)
|
||||
recipe_landscape_view: bool = sa.Column(sa.Boolean, default=False)
|
||||
recipe_disable_comments: bool = sa.Column(sa.Boolean, default=False)
|
||||
recipe_disable_amount: bool = sa.Column(sa.Boolean, default=False)
|
||||
|
||||
@auto_init()
|
||||
def __init__(self, **_) -> None:
|
||||
pass
|
@ -28,6 +28,7 @@ class User(SqlAlchemyBase, BaseMixins):
|
||||
email = Column(String, unique=True, index=True)
|
||||
password = Column(String)
|
||||
admin = Column(Boolean, default=False)
|
||||
advanced = Column(Boolean, default=False)
|
||||
|
||||
group_id = Column(Integer, ForeignKey("groups.id"))
|
||||
group = orm.relationship("Group", back_populates="users")
|
||||
@ -51,6 +52,7 @@ class User(SqlAlchemyBase, BaseMixins):
|
||||
favorite_recipes: list[str] = None,
|
||||
group: str = settings.DEFAULT_GROUP,
|
||||
admin=False,
|
||||
advanced=False,
|
||||
**_
|
||||
) -> None:
|
||||
|
||||
@ -61,6 +63,7 @@ class User(SqlAlchemyBase, BaseMixins):
|
||||
self.group = Group.get_ref(session, group)
|
||||
self.admin = admin
|
||||
self.password = password
|
||||
self.advanced = advanced
|
||||
|
||||
self.favorite_recipes = [
|
||||
RecipeModel.get_ref(session=session, match_value=x, match_attr="slug") for x in favorite_recipes
|
||||
@ -69,13 +72,26 @@ class User(SqlAlchemyBase, BaseMixins):
|
||||
if self.username is None:
|
||||
self.username = full_name
|
||||
|
||||
def update(self, full_name, email, group, admin, username, session=None, favorite_recipes=None, password=None, **_):
|
||||
def update(
|
||||
self,
|
||||
full_name,
|
||||
email,
|
||||
group,
|
||||
admin,
|
||||
username,
|
||||
session=None,
|
||||
favorite_recipes=None,
|
||||
password=None,
|
||||
advanced=False,
|
||||
**_
|
||||
):
|
||||
favorite_recipes = favorite_recipes or []
|
||||
self.username = username
|
||||
self.full_name = full_name
|
||||
self.email = email
|
||||
self.group = Group.get_ref(session, group)
|
||||
self.admin = admin
|
||||
self.advanced = advanced
|
||||
|
||||
if self.username is None:
|
||||
self.username = full_name
|
||||
|
@ -1,7 +1,6 @@
|
||||
import operator
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from pprint import pprint
|
||||
|
||||
from fastapi import BackgroundTasks, Depends, File, HTTPException, UploadFile, status
|
||||
from sqlalchemy.orm.session import Session
|
||||
@ -97,8 +96,6 @@ def import_database(
|
||||
rebase=import_data.rebase,
|
||||
)
|
||||
|
||||
pprint(db_import)
|
||||
|
||||
background_tasks.add_task(create_backup_event, "Database Restore", f"Restore File: {file_name}", session)
|
||||
return db_import
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from mealie.services.base_http_service import RouterFactory
|
||||
from mealie.services.cookbook.cookbook_service import CookbookService
|
||||
from mealie.services.group.webhook_service import WebhookService
|
||||
from mealie.services._base_http_service import RouterFactory
|
||||
from mealie.services.group_services import CookbookService, WebhookService
|
||||
|
||||
from . import categories, crud, self_service
|
||||
|
||||
|
@ -2,7 +2,7 @@ from fastapi import Depends
|
||||
|
||||
from mealie.routes.routers import UserAPIRouter
|
||||
from mealie.schema.recipe.recipe_category import CategoryBase
|
||||
from mealie.services.group.group_service import GroupSelfService
|
||||
from mealie.services.group_services.group_service import GroupSelfService
|
||||
|
||||
user_router = UserAPIRouter(prefix="/groups/categories", tags=["Groups: Mealplan Categories"])
|
||||
|
||||
|
@ -1,14 +1,27 @@
|
||||
from fastapi import Depends
|
||||
|
||||
from mealie.routes.routers import UserAPIRouter
|
||||
from mealie.schema.group.group_preferences import ReadGroupPreferences, UpdateGroupPreferences
|
||||
from mealie.schema.user.user import GroupInDB
|
||||
from mealie.services.group.group_service import GroupSelfService
|
||||
from mealie.services.group_services.group_service import GroupSelfService
|
||||
|
||||
user_router = UserAPIRouter(prefix="/groups/self", tags=["Groups: Self Service"])
|
||||
user_router = UserAPIRouter(prefix="/groups", tags=["Groups: Self Service"])
|
||||
|
||||
|
||||
@user_router.get("", response_model=GroupInDB)
|
||||
async def get_logged_in_user_group(g_self_service: GroupSelfService = Depends(GroupSelfService.write_existing)):
|
||||
@user_router.get("/self", response_model=GroupInDB)
|
||||
async def get_logged_in_user_group(g_service: GroupSelfService = Depends(GroupSelfService.write_existing)):
|
||||
""" Returns the Group Data for the Current User """
|
||||
|
||||
return g_self_service.item
|
||||
return g_service.item
|
||||
|
||||
|
||||
@user_router.put("/preferences", response_model=ReadGroupPreferences)
|
||||
def update_group_preferences(
|
||||
new_pref: UpdateGroupPreferences, g_service: GroupSelfService = Depends(GroupSelfService.write_existing)
|
||||
):
|
||||
return g_service.update_preferences(new_pref).preferences
|
||||
|
||||
|
||||
@user_router.get("/preferences", response_model=ReadGroupPreferences)
|
||||
def get_group_preferences(g_service: GroupSelfService = Depends(GroupSelfService.write_existing)):
|
||||
return g_service.item.preferences
|
||||
|
@ -1,12 +1,14 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from . import api_tokens, crud, favorites, images, passwords, sign_up
|
||||
from . import api_tokens, crud, favorites, images, passwords, registration, sign_up
|
||||
|
||||
# Must be used because of the way FastAPI works with nested routes
|
||||
user_prefix = "/users"
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(registration.router, prefix=user_prefix, tags=["Users: Registration"])
|
||||
|
||||
router.include_router(sign_up.admin_router, prefix=user_prefix, tags=["Users: Sign-Up"])
|
||||
router.include_router(sign_up.public_router, prefix=user_prefix, tags=["Users: Sign-Up"])
|
||||
|
||||
|
@ -7,7 +7,7 @@ from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import UserAPIRouter
|
||||
from mealie.schema.user import ChangePassword
|
||||
from mealie.services.user.user_service import UserService
|
||||
from mealie.services.user_services import UserService
|
||||
|
||||
user_router = UserAPIRouter(prefix="")
|
||||
|
||||
|
14
mealie/routes/users/registration.py
Normal file
14
mealie/routes/users/registration.py
Normal file
@ -0,0 +1,14 @@
|
||||
from fastapi import APIRouter, Depends, status
|
||||
|
||||
from mealie.schema.user.registration import CreateUserRegistration
|
||||
from mealie.schema.user.user import UserOut
|
||||
from mealie.services.user_services.registration_service import RegistrationService
|
||||
|
||||
router = APIRouter(prefix="/register")
|
||||
|
||||
|
||||
@router.post("", response_model=UserOut, status_code=status.HTTP_201_CREATED)
|
||||
def reset_user_password(
|
||||
data: CreateUserRegistration, registration_service: RegistrationService = Depends(RegistrationService.public)
|
||||
):
|
||||
return registration_service.register_user(data)
|
25
mealie/schema/group/group_preferences.py
Normal file
25
mealie/schema/group/group_preferences.py
Normal file
@ -0,0 +1,25 @@
|
||||
from fastapi_camelcase import CamelModel
|
||||
|
||||
|
||||
class UpdateGroupPreferences(CamelModel):
|
||||
private_group: bool = False
|
||||
first_day_of_week: int = 0
|
||||
|
||||
# Recipe Defaults
|
||||
recipe_public: bool = True
|
||||
recipe_show_nutrition: bool = False
|
||||
recipe_show_assets: bool = False
|
||||
recipe_landscape_view: bool = False
|
||||
recipe_disable_comments: bool = False
|
||||
recipe_disable_amount: bool = False
|
||||
|
||||
|
||||
class CreateGroupPreferences(UpdateGroupPreferences):
|
||||
group_id: int
|
||||
|
||||
|
||||
class ReadGroupPreferences(CreateGroupPreferences):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
29
mealie/schema/user/registration.py
Normal file
29
mealie/schema/user/registration.py
Normal file
@ -0,0 +1,29 @@
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import validator
|
||||
from pydantic.types import constr
|
||||
|
||||
|
||||
class CreateUserRegistration(CamelModel):
|
||||
group: str = None
|
||||
group_token: str = None
|
||||
email: constr(to_lower=True, strip_whitespace=True)
|
||||
username: constr(to_lower=True, strip_whitespace=True)
|
||||
password: str
|
||||
password_confirm: str
|
||||
advanced: bool = False
|
||||
private: bool = False
|
||||
|
||||
@validator("password_confirm")
|
||||
@classmethod
|
||||
def passwords_match(cls, value, values):
|
||||
if "password" in values and value != values["password"]:
|
||||
raise ValueError("passwords do not match")
|
||||
return value
|
||||
|
||||
@validator("group_token", always=True)
|
||||
@classmethod
|
||||
def group_or_token(cls, value, values):
|
||||
if bool(value) is False and bool(values["group"]) is False:
|
||||
raise ValueError("group or group_token must be provided")
|
||||
|
||||
return value
|
@ -6,8 +6,8 @@ from pydantic.types import constr
|
||||
from pydantic.utils import GetterDict
|
||||
|
||||
from mealie.core.config import settings
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.users import User
|
||||
from mealie.schema.group.group_preferences import ReadGroupPreferences
|
||||
from mealie.schema.recipe import RecipeSummary
|
||||
|
||||
from ..meal_plan import MealPlanOut, ShoppingListOut
|
||||
@ -50,8 +50,9 @@ class UserBase(CamelModel):
|
||||
username: Optional[str]
|
||||
full_name: Optional[str] = None
|
||||
email: constr(to_lower=True, strip_whitespace=True)
|
||||
admin: bool
|
||||
admin: bool = False
|
||||
group: Optional[str]
|
||||
advanced: bool = False
|
||||
favorite_recipes: Optional[list[str]] = []
|
||||
|
||||
class Config:
|
||||
@ -128,16 +129,11 @@ class GroupInDB(UpdateGroup):
|
||||
users: Optional[list[UserOut]]
|
||||
mealplans: Optional[list[MealPlanOut]]
|
||||
shopping_lists: Optional[list[ShoppingListOut]]
|
||||
preferences: Optional[ReadGroupPreferences] = None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@classmethod
|
||||
def getter_dict(_cls, orm_model: Group):
|
||||
return {
|
||||
**GetterDict(orm_model),
|
||||
}
|
||||
|
||||
|
||||
class LongLiveTokenInDB(CreateToken):
|
||||
id: int
|
||||
|
@ -1 +0,0 @@
|
||||
from .cookbook_service import *
|
@ -1,2 +1,3 @@
|
||||
from .cookbook_service import *
|
||||
from .group_service import *
|
||||
from .webhook_service import *
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.database import get_database
|
||||
from mealie.schema.cookbook.cookbook import CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook
|
||||
from mealie.services.base_http_service.http_services import UserHttpService
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_group_event
|
||||
from mealie.utils.error_messages import ErrorMessages
|
||||
|
16
mealie/services/group_services/group_mixins.py
Normal file
16
mealie/services/group_services/group_mixins.py
Normal file
@ -0,0 +1,16 @@
|
||||
from mealie.db.database import get_database
|
||||
from mealie.schema.group.group_preferences import CreateGroupPreferences
|
||||
from mealie.schema.user.user import GroupBase, GroupInDB
|
||||
|
||||
|
||||
def create_new_group(session, g_base: GroupBase, g_preferences: CreateGroupPreferences = None) -> GroupInDB:
|
||||
db = get_database()
|
||||
created_group = db.groups.create(session, g_base)
|
||||
|
||||
g_preferences = g_preferences or CreateGroupPreferences(group_id=0)
|
||||
|
||||
g_preferences.group_id = created_group.id
|
||||
|
||||
db.group_preferences.create(session, g_preferences)
|
||||
|
||||
return created_group
|
@ -4,9 +4,10 @@ from fastapi import Depends, HTTPException, status
|
||||
|
||||
from mealie.core.dependencies.grouped import UserDeps
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.schema.group.group_preferences import UpdateGroupPreferences
|
||||
from mealie.schema.recipe.recipe_category import CategoryBase
|
||||
from mealie.schema.user.user import GroupInDB
|
||||
from mealie.services.base_http_service.http_services import UserHttpService
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_group_event
|
||||
|
||||
logger = get_logger(module=__name__)
|
||||
@ -41,8 +42,11 @@ class GroupSelfService(UserHttpService[int, str]):
|
||||
return self.item
|
||||
|
||||
def update_categories(self, new_categories: list[CategoryBase]):
|
||||
if not self.item:
|
||||
return
|
||||
self.item.categories = new_categories
|
||||
|
||||
return self.db.groups.update(self.session, self.group_id, self.item)
|
||||
|
||||
def update_preferences(self, new_preferences: UpdateGroupPreferences):
|
||||
self.db.group_preferences.update(self.session, self.group_id, new_preferences)
|
||||
|
||||
return self.populate_item()
|
@ -5,7 +5,7 @@ from fastapi import HTTPException, status
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.schema.group import ReadWebhook
|
||||
from mealie.schema.group.webhook import CreateWebhook, SaveWebhook
|
||||
from mealie.services.base_http_service.http_services import UserHttpService
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_group_event
|
||||
|
||||
logger = get_logger(module=__name__)
|
@ -8,7 +8,7 @@ from sqlalchemy.exc import IntegrityError
|
||||
from mealie.core.dependencies.grouped import PublicDeps, UserDeps
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.schema.recipe.recipe import CreateRecipe, Recipe
|
||||
from mealie.services.base_http_service.http_services import PublicHttpService
|
||||
from mealie.services._base_http_service.http_services import PublicHttpService
|
||||
from mealie.services.events import create_recipe_event
|
||||
|
||||
logger = get_logger(module=__name__)
|
||||
|
@ -55,7 +55,6 @@ def _exec_crf_test(input_text):
|
||||
|
||||
|
||||
def convert_list_to_crf_model(list_of_ingrdeint_text: list[str]):
|
||||
print(list_of_ingrdeint_text)
|
||||
crf_output = _exec_crf_test([pre_process_string(x) for x in list_of_ingrdeint_text])
|
||||
crf_models = [CRFIngredient(**ingredient) for ingredient in utils.import_data(crf_output.split("\n"))]
|
||||
|
||||
@ -82,6 +81,3 @@ def convert_crf_models_to_ingredients(crf_models: list[CRFIngredient]):
|
||||
if __name__ == "__main__":
|
||||
crf_models = convert_list_to_crf_model(INGREDIENT_TEXT)
|
||||
ingredients = convert_crf_models_to_ingredients(crf_models)
|
||||
|
||||
for ingredient in ingredients:
|
||||
print(ingredient.input)
|
||||
|
1
mealie/services/user_services/__init__.py
Normal file
1
mealie/services/user_services/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .user_service import *
|
61
mealie/services/user_services/registration_service.py
Normal file
61
mealie/services/user_services/registration_service.py
Normal file
@ -0,0 +1,61 @@
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.core.security import hash_password
|
||||
from mealie.schema.group.group_preferences import CreateGroupPreferences
|
||||
from mealie.schema.user.registration import CreateUserRegistration
|
||||
from mealie.schema.user.user import GroupBase, GroupInDB, PrivateUser, UserIn
|
||||
from mealie.services._base_http_service.http_services import PublicHttpService
|
||||
from mealie.services.events import create_user_event
|
||||
from mealie.services.group_services.group_mixins import create_new_group
|
||||
|
||||
logger = get_logger(module=__name__)
|
||||
|
||||
|
||||
class RegistrationService(PublicHttpService[int, str]):
|
||||
event_func = create_user_event
|
||||
|
||||
def populate_item() -> None:
|
||||
pass
|
||||
|
||||
def register_user(self, registration: CreateUserRegistration) -> PrivateUser:
|
||||
self.registration = registration
|
||||
|
||||
logger.info(f"Registering user {registration.username}")
|
||||
|
||||
if registration.group:
|
||||
group = self._create_new_group()
|
||||
else:
|
||||
group = self._existing_group_ref()
|
||||
|
||||
return self._create_new_user(group)
|
||||
|
||||
def _create_new_user(self, group: GroupInDB) -> PrivateUser:
|
||||
new_user = UserIn(
|
||||
email=self.registration.email,
|
||||
username=self.registration.username,
|
||||
password=hash_password(self.registration.password),
|
||||
full_name=self.registration.username,
|
||||
advanced=self.registration.advanced,
|
||||
group=group.name,
|
||||
)
|
||||
|
||||
return self.db.users.create(self.session, new_user)
|
||||
|
||||
def _create_new_group(self) -> GroupInDB:
|
||||
group_data = GroupBase(name=self.registration.group)
|
||||
|
||||
group_preferences = CreateGroupPreferences(
|
||||
group_id=0,
|
||||
private_group=self.registration.private,
|
||||
first_day_of_week=0,
|
||||
recipe_public=not self.registration.private,
|
||||
recipe_show_nutrition=self.registration.advanced,
|
||||
recipe_show_assets=self.registration.advanced,
|
||||
recipe_landscape_view=False,
|
||||
recipe_disable_comments=self.registration.advanced,
|
||||
recipe_disable_amount=self.registration.advanced,
|
||||
)
|
||||
|
||||
return create_new_group(self.session, group_data, group_preferences)
|
||||
|
||||
def _existing_group_ref(self) -> GroupInDB:
|
||||
pass
|
@ -3,7 +3,7 @@ from fastapi import HTTPException, status
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.core.security import hash_password, verify_password
|
||||
from mealie.schema.user.user import ChangePassword, PrivateUser
|
||||
from mealie.services.base_http_service.http_services import UserHttpService
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_user_event
|
||||
|
||||
logger = get_logger(module=__name__)
|
@ -1,43 +0,0 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.app_routes import AppRoutes
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def page_data():
|
||||
return {"name": "My New Page", "description": "", "position": 0, "categories": [], "groupId": 1}
|
||||
|
||||
|
||||
def test_create_cookbook(api_client: TestClient, api_routes: AppRoutes, admin_token, page_data):
|
||||
response = api_client.post(api_routes.group_cookbook, json=page_data, headers=admin_token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_read_cookbook(api_client: TestClient, api_routes: AppRoutes, page_data, admin_token):
|
||||
response = api_client.get(api_routes.group_cookbook_id(1), headers=admin_token)
|
||||
|
||||
page_data["id"] = 1
|
||||
page_data["slug"] = "my-new-page"
|
||||
|
||||
assert json.loads(response.text) == page_data
|
||||
|
||||
|
||||
def test_update_cookbook(api_client: TestClient, api_routes: AppRoutes, page_data, admin_token):
|
||||
page_data["id"] = 1
|
||||
page_data["name"] = "My New Name"
|
||||
response = api_client.put(api_routes.group_cookbook_id(1), json=page_data, headers=admin_token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_delete_cookbook(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||
response = api_client.delete(api_routes.group_cookbook_id(1), headers=admin_token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.group_cookbook_id(1), headers=admin_token)
|
||||
assert response.status_code == 404
|
@ -4,6 +4,7 @@ import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.app_routes import AppRoutes
|
||||
from tests.utils.assertion_helpers import assert_ignore_keys
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -41,8 +42,10 @@ def test_update_group(api_client: TestClient, api_routes: AppRoutes, admin_token
|
||||
# Validate Changes
|
||||
response = api_client.get(api_routes.groups, headers=admin_token)
|
||||
all_groups = json.loads(response.text)
|
||||
|
||||
id_2 = filter(lambda x: x["id"] == 2, all_groups)
|
||||
assert next(id_2) == new_data
|
||||
|
||||
assert_ignore_keys(new_data, next(id_2), ["preferences"])
|
||||
|
||||
|
||||
def test_home_group_not_deletable(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||
|
@ -13,7 +13,7 @@ def backup_data():
|
||||
"force": True,
|
||||
"recipes": True,
|
||||
"settings": False, # ! Broken
|
||||
"groups": True,
|
||||
"groups": False, # ! Also Broken
|
||||
"users": True,
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,46 @@
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
class Routes:
|
||||
base = "/api/groups/cookbooks"
|
||||
|
||||
def item(item_id: int) -> str:
|
||||
return f"{Routes.base}/{item_id}"
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def page_data():
|
||||
return {"name": "My New Page", "description": "", "position": 0, "categories": [], "groupId": 1}
|
||||
|
||||
|
||||
def test_create_cookbook(api_client: TestClient, admin_token, page_data):
|
||||
response = api_client.post(Routes.base, json=page_data, headers=admin_token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_read_cookbook(api_client: TestClient, page_data, admin_token):
|
||||
response = api_client.get(Routes.item(1), headers=admin_token)
|
||||
|
||||
page_data["id"] = 1
|
||||
page_data["slug"] = "my-new-page"
|
||||
|
||||
assert response.json() == page_data
|
||||
|
||||
|
||||
def test_update_cookbook(api_client: TestClient, page_data, admin_token):
|
||||
page_data["id"] = 1
|
||||
page_data["name"] = "My New Name"
|
||||
response = api_client.put(Routes.item(1), json=page_data, headers=admin_token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_delete_cookbook(api_client: TestClient, admin_token):
|
||||
response = api_client.delete(Routes.item(1), headers=admin_token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(Routes.item(1), headers=admin_token)
|
||||
assert response.status_code == 404
|
@ -0,0 +1,32 @@
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.user.registration import CreateUserRegistration
|
||||
|
||||
|
||||
class Routes:
|
||||
base = "/api/users/register"
|
||||
auth_token = "/api/auth/token"
|
||||
|
||||
|
||||
def test_user_registration_new_group(api_client: TestClient):
|
||||
registration = CreateUserRegistration(
|
||||
group="New Group Name",
|
||||
email="email@email.com",
|
||||
username="fake-user-name",
|
||||
password="fake-password",
|
||||
password_confirm="fake-password",
|
||||
advanced=False,
|
||||
private=False,
|
||||
)
|
||||
|
||||
response = api_client.post(Routes.base, json=registration.dict(by_alias=True))
|
||||
assert response.status_code == 201
|
||||
|
||||
# Login
|
||||
form_data = {"username": "email@email.com", "password": "fake-password"}
|
||||
|
||||
response = api_client.post(Routes.auth_token, form_data)
|
||||
assert response.status_code == 200
|
||||
token = response.json().get("access_token")
|
||||
|
||||
assert token is not None
|
@ -0,0 +1,51 @@
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.group.group_preferences import UpdateGroupPreferences
|
||||
from tests.utils.assertion_helpers import assert_ignore_keys
|
||||
|
||||
|
||||
class Routes:
|
||||
base = "/api/groups/self"
|
||||
preferences = "/api/groups/preferences"
|
||||
|
||||
|
||||
def test_get_preferences(api_client: TestClient, admin_token) -> None:
|
||||
response = api_client.get(Routes.preferences, headers=admin_token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
preferences = response.json()
|
||||
|
||||
# Spot Check Defaults
|
||||
assert preferences["recipePublic"] is True
|
||||
assert preferences["recipeShowNutrition"] is False
|
||||
|
||||
|
||||
def test_preferences_in_group(api_client: TestClient, admin_token) -> None:
|
||||
response = api_client.get(Routes.base, headers=admin_token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
group = response.json()
|
||||
|
||||
assert group["preferences"] is not None
|
||||
|
||||
# Spot Check
|
||||
assert group["preferences"]["recipePublic"] is True
|
||||
assert group["preferences"]["recipeShowNutrition"] is False
|
||||
|
||||
|
||||
def test_update_preferences(api_client: TestClient, admin_token) -> None:
|
||||
new_data = UpdateGroupPreferences(recipe_public=False, recipe_show_nutrition=True)
|
||||
|
||||
response = api_client.put(Routes.preferences, json=new_data.dict(), headers=admin_token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
preferences = response.json()
|
||||
|
||||
assert preferences is not None
|
||||
assert preferences["recipePublic"] is False
|
||||
assert preferences["recipeShowNutrition"] is True
|
||||
|
||||
assert_ignore_keys(new_data.dict(by_alias=True), preferences, ["id", "groupId"])
|
@ -0,0 +1,71 @@
|
||||
import pytest
|
||||
|
||||
from mealie.schema.user.registration import CreateUserRegistration
|
||||
|
||||
|
||||
def test_create_user_registration() -> None:
|
||||
CreateUserRegistration(
|
||||
group="Home",
|
||||
group_token=None,
|
||||
email="SomeValidEmail@email.com",
|
||||
username="SomeValidUsername",
|
||||
password="SomeValidPassword",
|
||||
password_confirm="SomeValidPassword",
|
||||
advanced=False,
|
||||
private=True,
|
||||
)
|
||||
|
||||
CreateUserRegistration(
|
||||
group=None,
|
||||
group_token="asdfadsfasdfasdfasdf",
|
||||
email="SomeValidEmail@email.com",
|
||||
username="SomeValidUsername",
|
||||
password="SomeValidPassword",
|
||||
password_confirm="SomeValidPassword",
|
||||
advanced=False,
|
||||
private=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("group, group_token", [(None, None), ("", None), (None, "")])
|
||||
def test_group_or_token_validator(group, group_token) -> None:
|
||||
with pytest.raises(ValueError):
|
||||
CreateUserRegistration(
|
||||
group=group,
|
||||
group_token=group_token,
|
||||
email="SomeValidEmail@email.com",
|
||||
username="SomeValidUsername",
|
||||
password="SomeValidPassword",
|
||||
password_confirm="SomeValidPassword",
|
||||
advanced=False,
|
||||
private=True,
|
||||
)
|
||||
|
||||
|
||||
def test_group_no_args_passed() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
CreateUserRegistration(
|
||||
email="SomeValidEmail@email.com",
|
||||
username="SomeValidUsername",
|
||||
password="SomeValidPassword",
|
||||
password_confirm="SomeValidPassword",
|
||||
advanced=False,
|
||||
private=True,
|
||||
)
|
||||
|
||||
|
||||
def test_password_validator() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
CreateUserRegistration(
|
||||
group=None,
|
||||
group_token="asdfadsfasdfasdfasdf",
|
||||
email="SomeValidEmail@email.com",
|
||||
username="SomeValidUsername",
|
||||
password="SomeValidPassword",
|
||||
password_confirm="PasswordDefNotMatch",
|
||||
advanced=False,
|
||||
private=True,
|
||||
)
|
||||
|
||||
|
||||
test_create_user_registration()
|
17
tests/utils/assertion_helpers.py
Normal file
17
tests/utils/assertion_helpers.py
Normal file
@ -0,0 +1,17 @@
|
||||
def assert_ignore_keys(dict1: dict, dict2: dict, ignore_keys: list) -> None:
|
||||
"""
|
||||
Itterates through a list of keys and checks if they are in the the provided ignore_keys list,
|
||||
if they are not in the ignore_keys list, it checks the value of the key in the provided against
|
||||
the value provided in dict2. If the value of the key in dict1 is not equal to the value of the
|
||||
key in dict2, The assertion fails. Useful for testing id / group_id agnostic data
|
||||
|
||||
Note: ignore_keys defaults to ['id', 'group_id']
|
||||
"""
|
||||
if ignore_keys is None:
|
||||
ignore_keys = ["id", "group_id"]
|
||||
|
||||
for key, value in dict1.items():
|
||||
if key in ignore_keys:
|
||||
continue
|
||||
else:
|
||||
assert value == dict2[key]
|
Loading…
x
Reference in New Issue
Block a user