diff --git a/frontend/components/Domain/User/UserRegistrationForm.vue b/frontend/components/Domain/User/UserRegistrationForm.vue new file mode 100644 index 00000000000..8b1b945f9fd --- /dev/null +++ b/frontend/components/Domain/User/UserRegistrationForm.vue @@ -0,0 +1,160 @@ + + + + {{ $globals.icons.user }} + {{ $t("user-registration.account-details") }} + + + + + + + + + + + + + + + + {{ $tc("user.enable-advanced-content-description") }} + + + + + + + + + + diff --git a/frontend/components/global/AutoForm.vue b/frontend/components/global/AutoForm.vue index f92acf2e975..2f143f52ea6 100644 --- a/frontend/components/global/AutoForm.vue +++ b/frontend/components/global/AutoForm.vue @@ -15,12 +15,22 @@ v-if="inputField.type === fieldTypes.BOOLEAN" v-model="value[inputField.varName]" class="my-0 py-0" - :label="inputField.label" :name="inputField.varName" - :hint="inputField.hint || ''" :disabled="(inputField.disableUpdate && updateMode) || (!updateMode && inputField.disableCreate) || (disabledFields && disabledFields.includes(inputField.varName))" @change="emitBlur" - /> + > + + + + {{ inputField.label }} + + + {{ inputField.hint }} + + + + + + + + + + + Mealie + + + + + + + + + + + + + + + + + + + + + + {{ prevButtonIconRef }} + + {{ prevButtonTextRef }} + + + + + + + + + {{ nextButtonIconRef }} + + {{ nextButtonTextRef }} + + {{ nextButtonIconRef }} + + + + + + + {{ $globals.icons.translate }} + {{ $t("language-dialog.choose-language") }} + + + + + + + + + diff --git a/frontend/composables/use-setup/common-settings-form.ts b/frontend/composables/use-setup/common-settings-form.ts new file mode 100644 index 00000000000..12b3a7496e5 --- /dev/null +++ b/frontend/composables/use-setup/common-settings-form.ts @@ -0,0 +1,30 @@ +import { useContext } from "@nuxtjs/composition-api"; +import { fieldTypes } from "../forms"; +import { AutoFormItems } from "~/types/auto-forms"; + +export const useCommonSettingsForm = () => { + const { i18n } = useContext(); + + const commonSettingsForm: AutoFormItems = [ + { + section: i18n.tc("profile.group-settings"), + label: i18n.tc("group.enable-public-access"), + hint: i18n.tc("group.enable-public-access-description"), + varName: "makeGroupRecipesPublic", + type: fieldTypes.BOOLEAN, + rules: ["required"], + }, + { + section: i18n.tc("data-pages.data-management"), + label: i18n.tc("user-registration.use-seed-data"), + hint: i18n.tc("user-registration.use-seed-data-description"), + varName: "useSeedData", + type: fieldTypes.BOOLEAN, + rules: ["required"], + }, + ]; + + return { + commonSettingsForm, + } +} diff --git a/frontend/composables/use-setup/index.ts b/frontend/composables/use-setup/index.ts new file mode 100644 index 00000000000..607d0ede618 --- /dev/null +++ b/frontend/composables/use-setup/index.ts @@ -0,0 +1 @@ +export { useCommonSettingsForm } from "./common-settings-form"; diff --git a/frontend/composables/use-users/index.ts b/frontend/composables/use-users/index.ts index adacee24eb8..f1b1a6a8571 100644 --- a/frontend/composables/use-users/index.ts +++ b/frontend/composables/use-users/index.ts @@ -1 +1,2 @@ export { useUserForm } from "./user-form"; +export { useUserRegistrationForm } from "./user-registration-form"; diff --git a/frontend/composables/use-users/user-registration-form.ts b/frontend/composables/use-users/user-registration-form.ts new file mode 100644 index 00000000000..af27549bb1c --- /dev/null +++ b/frontend/composables/use-users/user-registration-form.ts @@ -0,0 +1,87 @@ +import { ref, Ref, useContext } from "@nuxtjs/composition-api"; +import { useAsyncValidator } from "~/composables/use-validators"; +import { VForm } from "~/types/vuetify"; +import { usePublicApi } from "~/composables/api/api-client"; + +const domAccountForm = ref(null); +const username = ref(""); +const fullName = ref(""); +const email = ref(""); +const password1 = ref(""); +const password2 = ref(""); +const advancedOptions = ref(false); + +export const useUserRegistrationForm = () => { + const { i18n } = useContext(); + function safeValidate(form: Ref) { + if (form.value && form.value.validate) { + return form.value.validate(); + } + return false; + } + // ================================================================ + // Provide Group Details + const publicApi = usePublicApi(); + // ================================================================ + // Provide Account Details + + const usernameErrorMessages = ref([]); + const { validate: validateUsername, valid: validUsername } = useAsyncValidator( + username, + (v: string) => publicApi.validators.username(v), + i18n.tc("validation.username-is-taken"), + usernameErrorMessages + ); + const emailErrorMessages = ref([]); + const { validate: validateEmail, valid: validEmail } = useAsyncValidator( + email, + (v: string) => publicApi.validators.email(v), + i18n.tc("validation.email-is-taken"), + emailErrorMessages + ); + const accountDetails = { + username, + fullName, + email, + advancedOptions, + validate: async () => { + if (!(validUsername.value && validEmail.value)) { + await Promise.all([validateUsername(), validateEmail()]); + } + + return (safeValidate(domAccountForm as Ref) && validUsername.value && validEmail.value); + }, + reset: () => { + accountDetails.username.value = ""; + accountDetails.fullName.value = ""; + accountDetails.email.value = ""; + accountDetails.advancedOptions.value = false; + }, + }; + // ================================================================ + // Provide Credentials + const passwordMatch = () => password1.value === password2.value || i18n.tc("user.password-must-match"); + const credentials = { + password1, + password2, + passwordMatch, + reset: () => { + credentials.password1.value = ""; + credentials.password2.value = ""; + } + }; + + return { + accountDetails, + credentials, + emailErrorMessages, + usernameErrorMessages, + // Fields + advancedOptions, + // Validators + validateUsername, + validateEmail, + // Dom Refs + domAccountForm, + }; +}; diff --git a/frontend/lang/messages/en-US.json b/frontend/lang/messages/en-US.json index aeda8952029..4cd655b7c31 100644 --- a/frontend/lang/messages/en-US.json +++ b/frontend/lang/messages/en-US.json @@ -85,6 +85,7 @@ "clear": "Clear", "close": "Close", "confirm": "Confirm", + "confirm-how-does-everything-look": "How does everything look?", "confirm-delete-generic": "Are you sure you want to delete this?", "copied_message": "Copied!", "create": "Create", @@ -170,6 +171,7 @@ "units": "Units", "back": "Back", "next": "Next", + "start": "Start", "toggle-view": "Toggle View", "date": "Date", "id": "Id", @@ -238,6 +240,8 @@ "group-preferences": "Group Preferences", "private-group": "Private Group", "private-group-description": "Setting your group to private will default all public view options to default. This overrides an individual recipes public view settings.", + "enable-public-access": "Enable Public Access", + "enable-public-access-description": "Make group recipes public by default, and allow visitors to view recipes without logging-in", "allow-users-outside-of-your-group-to-see-your-recipes": "Allow users outside of your group to see your recipes", "allow-users-outside-of-your-group-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your group or with a pre-generated private link", "show-nutrition-information": "Show nutrition information", @@ -351,6 +355,7 @@ }, "recipe-data-migrations": "Recipe Data Migrations", "recipe-data-migrations-explanation": "Recipes can be migrated from another supported application to Mealie. This is a great way to get started with Mealie.", + "coming-from-another-application-or-an-even-older-version-of-mealie": "Coming from another application or an even older version of Mealie? Check out migrations and see if your data can be imported.", "choose-migration-type": "Choose Migration Type", "tag-all-recipes": "Tag all recipes with {tag-name} tag", "nextcloud-text": "Nextcloud recipes can be imported from a zip file that contains the data stored in Nextcloud. See the example folder structure below to ensure your recipes are able to be imported.", @@ -533,6 +538,8 @@ "looking-for-migrations": "Looking For Migrations?", "import-with-url": "Import with URL", "create-recipe": "Create Recipe", + "create-recipe-description": "Create a new recipe from scratch.", + "create-recipes": "Create Recipes", "import-with-zip": "Import with .zip", "create-recipe-from-an-image": "Create recipe from an image", "bulk-url-import": "Bulk URL Import", @@ -843,6 +850,7 @@ "or": "or", "logout": "Logout", "manage-users": "Manage Users", + "manage-users-description": "Create and manage users.", "new-password": "New Password", "new-user": "New User", "password-has-been-reset-to-the-default-password": "Password has been reset to the default password", @@ -1138,7 +1146,17 @@ "background-tasks": "Background Tasks", "background-tasks-description": "Here you can view all the running background tasks and their status", "no-logs-found": "No Logs Found", - "tasks": "Tasks" + "tasks": "Tasks", + "setup": { + "first-time-setup": "First Time Setup", + "welcome-to-mealie-get-started": "Welcome to Mealie! Let's get started", + "already-set-up-bring-to-homepage": "I'm already set up, just bring me to the homepage", + "common-settings-for-new-sites": "Here are some common settings for new sites", + "setup-complete": "Setup Complete!", + "here-are-a-few-things-to-help-you-get-started": "Here are a few things to help you get started with Mealie", + "restore-from-v1-backup": "Have a backup from a previous instance of Mealie v1? You can restore it here.", + "manage-profile-or-get-invite-link": "Manage your own profile, or grab an invite link to share with others." + } }, "profile": { "welcome-user": "👋 Welcome, {0}", diff --git a/frontend/lib/api/types/admin.ts b/frontend/lib/api/types/admin.ts index 7f3e883c600..bdf98d9e9a0 100644 --- a/frontend/lib/api/types/admin.ts +++ b/frontend/lib/api/types/admin.ts @@ -43,6 +43,7 @@ export interface AppInfo { } export interface AppStartupInfo { isFirstLogin: boolean; + isDemo: boolean; } export interface AppStatistics { totalRecipes: number; diff --git a/frontend/lib/api/types/user.ts b/frontend/lib/api/types/user.ts index 32ec9767f33..970a05d2705 100644 --- a/frontend/lib/api/types/user.ts +++ b/frontend/lib/api/types/user.ts @@ -22,6 +22,7 @@ export interface CreateUserRegistration { groupToken?: string; email: string; username: string; + fullName: string; password: string; passwordConfirm: string; advanced?: boolean; diff --git a/frontend/lib/api/user/user-registration.ts b/frontend/lib/api/user/user-registration.ts index ea722c5fbc5..e1921d4cf3e 100644 --- a/frontend/lib/api/user/user-registration.ts +++ b/frontend/lib/api/user/user-registration.ts @@ -8,8 +8,6 @@ const routes = { }; export class RegisterAPI extends BaseAPI { - /** Returns a list of available .zip files for import into Mealie. - */ async register(payload: CreateUserRegistration) { return await this.requests.post(routes.register, payload); } diff --git a/frontend/pages/admin/setup.vue b/frontend/pages/admin/setup.vue new file mode 100644 index 00000000000..6541b289b56 --- /dev/null +++ b/frontend/pages/admin/setup.vue @@ -0,0 +1,431 @@ + + + + + + {{ $i18n.tc('admin.setup.welcome-to-mealie-get-started') }} + + + {{ $i18n.tc('admin.setup.already-set-up-bring-to-homepage') }} + + + + + + + + {{ $i18n.tc('admin.setup.common-settings-for-new-sites') }} + + + + + + {{ $t("general.confirm-how-does-everything-look") }} + + + + + + {{ item.text }} + {{ item.value }} + + + + + + + + + {{ $i18n.tc('admin.setup.setup-complete') }} + + + {{ $i18n.tc('admin.setup.here-are-a-few-things-to-help-you-get-started') }} + + + + + + {{ link.section }} + + + + {{ link.text }} + + + {{ link.description }} + + + + + + + + diff --git a/frontend/pages/index.vue b/frontend/pages/index.vue index 795b423388c..c6fb9dc33ab 100644 --- a/frontend/pages/index.vue +++ b/frontend/pages/index.vue @@ -3,8 +3,9 @@ diff --git a/frontend/pages/login.vue b/frontend/pages/login.vue index a595a1a911e..5815e3dc710 100644 --- a/frontend/pages/login.vue +++ b/frontend/pages/login.vue @@ -152,14 +152,8 @@ export default defineComponent({ const { $auth, i18n, $axios } = useContext(); const { loggedIn } = useLoggedInState(); const groupSlug = computed(() => $auth.user?.groupSlug); - - whenever( - () => loggedIn.value && groupSlug.value, - () => { - router.push(`/g/${groupSlug.value || ""}`); - }, - { immediate: true }, - ); + const isDemo = ref(false); + const isFirstLogin = ref(false); const form = reactive({ email: "", @@ -167,12 +161,23 @@ export default defineComponent({ remember: false, }); - const isFirstLogin = ref(false) - useAsync(async () => { const data = await $axios.get("/api/app/about/startup-info"); + isDemo.value = data.data.isDemo; isFirstLogin.value = data.data.isFirstLogin; - }, useAsyncKey()); + }, useAsyncKey()); + + whenever( + () => loggedIn.value && groupSlug.value, + () => { + if (!isDemo.value && isFirstLogin.value && $auth.user?.admin) { + router.push("/admin/setup"); + } else { + router.push(`/g/${groupSlug.value || ""}`); + } + }, + { immediate: true }, + ); const loggingIn = ref(false); diff --git a/frontend/pages/register/index.vue b/frontend/pages/register/index.vue index 1cd1f74d88a..fb42ed396ee 100644 --- a/frontend/pages/register/index.vue +++ b/frontend/pages/register/index.vue @@ -4,7 +4,7 @@ fluid class="d-flex justify-center align-center" :class="{ - 'bg-off-white': !$vuetify.theme.dark && !isDark.value, + 'bg-off-white': !$vuetify.theme.dark && !isDark, }" > @@ -136,73 +136,14 @@ - - {{ $globals.icons.user }} - {{ $t("user-registration.account-details") }} - - - - - - - - - - - - - - - {{ $tc("user.enable-advanced-content-description") }} - - - - + {{ $globals.icons.back }} {{ $t("general.back") }} - + {{ $globals.icons.forward }} {{ $t("general.next") }} @@ -258,6 +199,7 @@ import { defineComponent, onMounted, ref, useRouter, Ref, useContext, computed } from "@nuxtjs/composition-api"; import { useDark } from "@vueuse/core"; import { States, RegistrationType, useRegistration } from "./states"; +import { useUserRegistrationForm } from "~/composables/use-users/user-registration-form"; import { useRouteQuery } from "~/composables/use-router"; import { validators, useAsyncValidator } from "~/composables/use-validators"; import { useUserApi } from "~/composables/api"; @@ -267,7 +209,7 @@ import { VForm } from "~/types/vuetify"; import { usePasswordField } from "~/composables/use-passwords"; import { usePublicApi } from "~/composables/api/api-client"; import { useLocales } from "~/composables/use-locales"; -import UserPasswordStrength from "~/components/Domain/User/UserPasswordStrength.vue"; +import UserRegistrationForm from "~/components/Domain/User/UserRegistrationForm.vue"; const inputAttrs = { filled: true, @@ -277,7 +219,7 @@ const inputAttrs = { }; export default defineComponent({ - components: { UserPasswordStrength }, + components: { UserRegistrationForm }, layout: "blank", setup() { const { i18n } = useContext(); @@ -370,48 +312,22 @@ export default defineComponent({ state.setState(States.ProvideAccountDetails); }, }; - // ================================================================ - // Provide Account Details - const domAccountForm = ref(null); - const username = ref(""); - const email = ref(""); - const advancedOptions = ref(false); - const usernameErrorMessages = ref([]); - const { validate: validateUsername, valid: validUsername } = useAsyncValidator( - username, - (v: string) => publicApi.validators.username(v), - i18n.tc("validation.username-is-taken"), - usernameErrorMessages - ); - const emailErrorMessages = ref([]); - const { validate: validateEmail, valid: validEmail } = useAsyncValidator( - email, - (v: string) => publicApi.validators.email(v), - i18n.tc("validation.email-is-taken"), - emailErrorMessages - ); - const accountDetails = { - username, - email, - advancedOptions, - next: () => { - if (!safeValidate(domAccountForm as Ref) || !validUsername.value || !validEmail.value) { + const pwFields = usePasswordField(); + const { + accountDetails, + credentials, + domAccountForm, + emailErrorMessages, + usernameErrorMessages, + validateUsername, + validateEmail, + } = useUserRegistrationForm(); + async function accountDetailsNext() { + if (!await accountDetails.validate()) { return; } state.setState(States.Confirmation); - }, - }; - // ================================================================ - // Provide Credentials - const password1 = ref(""); - const password2 = ref(""); - const pwFields = usePasswordField(); - const passwordMatch = () => password1.value === password2.value || i18n.tc("user.password-must-match"); - const credentials = { - password1, - password2, - passwordMatch, - }; + } // ================================================================ // Locale const { locale } = useLocales(); @@ -438,17 +354,22 @@ export default defineComponent({ { display: true, text: i18n.tc("user.email"), - value: email.value, + value: accountDetails.email.value, + }, + { + display: true, + text: i18n.tc("user.full-name"), + value: accountDetails.fullName.value, }, { display: true, text: i18n.tc("user.username"), - value: username.value, + value: accountDetails.username.value, }, { display: true, text: i18n.tc("user.enable-advanced-content"), - value: advancedOptions.value ? i18n.tc("general.yes") : i18n.tc("general.no"), + value: accountDetails.advancedOptions.value ? i18n.tc("general.yes") : i18n.tc("general.no"), }, ]; }); @@ -456,12 +377,13 @@ export default defineComponent({ const router = useRouter(); async function submitRegistration() { const payload: CreateUserRegistration = { - email: email.value, - username: username.value, - password: password1.value, - passwordConfirm: password2.value, + email: accountDetails.email.value, + username: accountDetails.username.value, + fullName: accountDetails.fullName.value, + password: credentials.password1.value, + passwordConfirm: credentials.password2.value, locale: locale.value, - advanced: advancedOptions.value, + advanced: accountDetails.advancedOptions.value, }; if (state.ctx.type === RegistrationType.CreateGroup) { payload.group = groupName.value; @@ -472,12 +394,17 @@ export default defineComponent({ } const { response } = await api.register.register(payload); if (response?.status === 201) { + accountDetails.reset(); + credentials.reset(); alert.success(i18n.tc("user-registration.registration-success")); router.push("/login"); + } else { + alert.error(i18n.tc("events.something-went-wrong")); } } return { accountDetails, + accountDetailsNext, confirmationData, credentials, emailErrorMessages, diff --git a/frontend/types/components.d.ts b/frontend/types/components.d.ts index a3db56fe621..d8f0b47c340 100644 --- a/frontend/types/components.d.ts +++ b/frontend/types/components.d.ts @@ -32,6 +32,7 @@ import ReportTable from "@/components/global/ReportTable.vue"; import SafeMarkdown from "@/components/global/SafeMarkdown.vue"; import StatsCards from "@/components/global/StatsCards.vue"; import ToggleState from "@/components/global/ToggleState.vue"; +import BaseWizard from "@/components/global/BaseWizard.vue"; import DefaultLayout from "@/components/layout/DefaultLayout.vue"; declare module "vue" { @@ -70,6 +71,7 @@ declare module "vue" { SafeMarkdown: typeof SafeMarkdown; StatsCards: typeof StatsCards; ToggleState: typeof ToggleState; + BaseWizard: typeof BaseWizard; // Layout Components DefaultLayout: typeof DefaultLayout; } diff --git a/mealie/routes/app/app_about.py b/mealie/routes/app/app_about.py index 8d412bb4ac1..1c080efdd9c 100644 --- a/mealie/routes/app/app_about.py +++ b/mealie/routes/app/app_about.py @@ -47,6 +47,7 @@ def get_startup_info(session: Session = Depends(generate_session)): return AppStartupInfo( is_first_login=is_first_login, + is_demo=settings.IS_DEMO, ) diff --git a/mealie/schema/admin/about.py b/mealie/schema/admin/about.py index 0ba91e3af16..e9ff92305a4 100644 --- a/mealie/schema/admin/about.py +++ b/mealie/schema/admin/about.py @@ -46,6 +46,8 @@ class AppStartupInfo(MealieModel): it is removed, this will always return False. """ + is_demo: bool + class AdminAboutInfo(AppInfo): versionLatest: str diff --git a/mealie/schema/user/registration.py b/mealie/schema/user/registration.py index 7219cd27718..3ce158acf5e 100644 --- a/mealie/schema/user/registration.py +++ b/mealie/schema/user/registration.py @@ -10,8 +10,9 @@ class CreateUserRegistration(MealieModel): group: str | None = None group_token: Annotated[str | None, Field(validate_default=True)] = None - email: Annotated[str, StringConstraints(to_lower=True, strip_whitespace=True)] # type: ignore - username: Annotated[str, StringConstraints(to_lower=True, strip_whitespace=True)] # type: ignore + email: Annotated[str, StringConstraints(to_lower=True, strip_whitespace=True)] + username: Annotated[str, StringConstraints(to_lower=True, strip_whitespace=True)] + full_name: Annotated[str, StringConstraints(strip_whitespace=True)] password: str password_confirm: str advanced: bool = False diff --git a/mealie/schema/user/user.py b/mealie/schema/user/user.py index 5803de0867d..e0deabdf704 100644 --- a/mealie/schema/user/user.py +++ b/mealie/schema/user/user.py @@ -49,7 +49,7 @@ class DeleteTokenResponse(MealieModel): class ChangePassword(MealieModel): - current_password: str + current_password: str = "" new_password: str = Field(..., min_length=8) diff --git a/mealie/services/user_services/registration_service.py b/mealie/services/user_services/registration_service.py index 5873e9c0d91..e6e84b567e3 100644 --- a/mealie/services/user_services/registration_service.py +++ b/mealie/services/user_services/registration_service.py @@ -27,7 +27,7 @@ def _create_new_user(self, group: GroupInDB, new_group: bool) -> PrivateUser: email=self.registration.email, username=self.registration.username, password=hash_password(self.registration.password), - full_name=self.registration.username, + full_name=self.registration.full_name, advanced=self.registration.advanced, group=group, can_invite=new_group, diff --git a/tests/e2e/login.spec.ts b/tests/e2e/login.spec.ts index 8439e3b9755..f225bcaa3cb 100644 --- a/tests/e2e/login.spec.ts +++ b/tests/e2e/login.spec.ts @@ -11,6 +11,8 @@ test('password login', async ({ page }) => { await page.locator('div').filter({ hasText: /^Password$/ }).nth(3).click(); await page.getByLabel('Password').fill(password); await page.getByRole('button', { name: 'Login', exact: true }).click(); + // skip admin setup page + await page.getByRole('link', { name: "I'm already set up, just bring me to the homepage" }).click(); await expect(page.getByRole('navigation')).toContainText(name); }); @@ -40,6 +42,8 @@ test('ldap admin login', async ({ page }) => { await page.locator('div').filter({ hasText: /^Password$/ }).nth(3).click(); await page.getByLabel('Password').fill(password); await page.getByRole('button', { name: 'Login', exact: true }).click(); + // skip admin setup page + await page.getByRole('link', { name: "I'm already set up, just bring me to the homepage" }).click(); await expect(page.getByRole('navigation')).toContainText(name); await expect(page.getByRole('link', { name: 'Settings' })).toBeVisible(); }); @@ -113,6 +117,8 @@ test('settings page verify oidc', async ({ page }) => { await page.getByLabel('Password').click(); await page.getByLabel('Password').fill('MyPassword'); await page.getByRole('button', { name: 'Login', exact: true }).click(); + // skip admin setup page + await page.getByRole('link', { name: "I'm already set up, just bring me to the homepage" }).click(); await page.getByRole('link', { name: 'Settings' }).click(); await page.getByRole('link', { name: 'Users' }).click(); await page.getByRole('cell', { name: username, exact: true }).click(); @@ -135,6 +141,8 @@ test('oidc admin user', async ({ page }) => { await page.getByPlaceholder('Enter any user/subject').fill(username); await page.getByPlaceholder('Optional claims JSON value,').fill(JSON.stringify(claims)); await page.getByRole('button', { name: 'Sign-in' }).click(); + // skip admin setup page + await page.getByRole('link', { name: "I'm already set up, just bring me to the homepage" }).click(); await expect(page.getByRole('navigation')).toContainText(name); await expect(page.getByRole('link', { name: 'Settings' })).toBeVisible(); }); diff --git a/tests/integration_tests/user_tests/test_user_password_reset_service.py b/tests/integration_tests/user_tests/test_user_password_services.py similarity index 89% rename from tests/integration_tests/user_tests/test_user_password_reset_service.py rename to tests/integration_tests/user_tests/test_user_password_services.py index 34dd58ab47e..0c8433885fe 100644 --- a/tests/integration_tests/user_tests/test_user_password_reset_service.py +++ b/tests/integration_tests/user_tests/test_user_password_services.py @@ -3,11 +3,14 @@ import pytest from fastapi.testclient import TestClient +from mealie.core.config import get_app_settings from mealie.db.db_setup import session_context -from mealie.schema.user.user import PrivateUser +from mealie.repos.repository_factory import AllRepositories +from mealie.schema.response.pagination import PaginationQuery +from mealie.schema.user.user import ChangePassword, PrivateUser from mealie.services.user_services.password_reset_service import PasswordResetService from tests.utils import api_routes -from tests.utils.factories import random_string +from tests.utils.factories import random_email, random_string from tests.utils.fixture_schemas import TestUser diff --git a/tests/unit_tests/validator_tests/test_registration_validators.py b/tests/unit_tests/validator_tests/test_registration_validators.py index 1c3755ffffb..e749ea4a738 100644 --- a/tests/unit_tests/validator_tests/test_registration_validators.py +++ b/tests/unit_tests/validator_tests/test_registration_validators.py @@ -9,6 +9,7 @@ def test_create_user_registration() -> None: group_token=None, email="SomeValidEmail@example.com", username="SomeValidUsername", + full_name="SomeValidFullName", password="SomeValidPassword", password_confirm="SomeValidPassword", advanced=False, @@ -20,6 +21,7 @@ def test_create_user_registration() -> None: group_token="asdfadsfasdfasdfasdf", email="SomeValidEmail@example.com", username="SomeValidUsername", + full_name="SomeValidFullName", password="SomeValidPassword", password_confirm="SomeValidPassword", advanced=False, @@ -35,6 +37,7 @@ def test_group_or_token_validator(group, group_token) -> None: group_token=group_token, email="SomeValidEmail@example.com", username="SomeValidUsername", + full_name="SomeValidFullName", password="SomeValidPassword", password_confirm="SomeValidPassword", advanced=False, @@ -47,6 +50,7 @@ def test_group_no_args_passed() -> None: CreateUserRegistration( email="SomeValidEmail@example.com", username="SomeValidUsername", + full_name="SomeValidFullName", password="SomeValidPassword", password_confirm="SomeValidPassword", advanced=False, @@ -61,6 +65,7 @@ def test_password_validator() -> None: group_token="asdfadsfasdfasdfasdf", email="SomeValidEmail@example.com", username="SomeValidUsername", + full_name="SomeValidFullName", password="SomeValidPassword", password_confirm="PasswordDefNotMatch", advanced=False, diff --git a/tests/utils/factories.py b/tests/utils/factories.py index 57d613b3e2e..ac1ceb56173 100644 --- a/tests/utils/factories.py +++ b/tests/utils/factories.py @@ -25,6 +25,7 @@ def user_registration_factory(advanced=None, private=None) -> CreateUserRegistra group=random_string(), email=random_email(), username=random_string(), + full_name=random_string(), password="fake-password", password_confirm="fake-password", advanced=advanced or random_bool(),
+ {{ $tc("user.enable-advanced-content-description") }} +
- {{ $tc("user.enable-advanced-content-description") }} -