1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-02 20:15:24 +02:00

feat: auto detect first login (#2722)

* 'hide' default email and password env variables

* first login API endpoint

* run code-generators

* frontend indicators for default username and pw

* remove old env variables from docs

* fix env set variable

* remove password from tests
This commit is contained in:
Hayden 2023-11-15 09:24:24 -06:00 committed by GitHub
parent 71f95ca3c6
commit bc575ec5ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 234 additions and 120 deletions

View file

@ -4,19 +4,17 @@
### General ### General
| Variables | Default | Description | | Variables | Default | Description |
| ---------------- | :-------------------: | ----------------------------------------------------------------------------------- | | ------------- | :-------------------: | ----------------------------------------------------------------------------------- |
| PUID | 911 | UserID permissions between host OS and container | | PUID | 911 | UserID permissions between host OS and container |
| PGID | 911 | GroupID permissions between host OS and container | | PGID | 911 | GroupID permissions between host OS and container |
| DEFAULT_GROUP | Home | The default group for users | | DEFAULT_GROUP | Home | The default group for users |
| DEFAULT_EMAIL | changeme@example.com | The default username for the superuser | | BASE_URL | http://localhost:8080 | Used for Notifications |
| DEFAULT_PASSWORD | MyPassword | The default password for the superuser | | TOKEN_TIME | 48 | The time in hours that a login/auth token is valid |
| BASE_URL | http://localhost:8080 | Used for Notifications | | API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** |
| TOKEN_TIME | 48 | The time in hours that a login/auth token is valid | | API_DOCS | True | Turns on/off access to the API documentation locally. |
| API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** | | TZ | UTC | Must be set to get correct date/time on the server |
| API_DOCS | True | Turns on/off access to the API documentation locally. | | ALLOW_SIGNUP | true | Allow user sign-up without token |
| TZ | UTC | Must be set to get correct date/time on the server |
| ALLOW_SIGNUP | true | Allow user sign-up without token |
### Security ### Security

View file

@ -3,117 +3,117 @@ export const LOCALES = [
{ {
name: "繁體中文 (Chinese traditional)", name: "繁體中文 (Chinese traditional)",
value: "zh-TW", value: "zh-TW",
progress: 26, progress: 28,
}, },
{ {
name: "简体中文 (Chinese simplified)", name: "简体中文 (Chinese simplified)",
value: "zh-CN", value: "zh-CN",
progress: 34, progress: 65,
}, },
{ {
name: "Tiếng Việt (Vietnamese)", name: "Tiếng Việt (Vietnamese)",
value: "vi-VN", value: "vi-VN",
progress: 0, progress: 2,
}, },
{ {
name: "Українська (Ukrainian)", name: "Українська (Ukrainian)",
value: "uk-UA", value: "uk-UA",
progress: 100, progress: 99,
}, },
{ {
name: "Türkçe (Turkish)", name: "Türkçe (Turkish)",
value: "tr-TR", value: "tr-TR",
progress: 47, progress: 50,
}, },
{ {
name: "Svenska (Swedish)", name: "Svenska (Swedish)",
value: "sv-SE", value: "sv-SE",
progress: 60, progress: 71,
}, },
{ {
name: "српски (Serbian)", name: "српски (Serbian)",
value: "sr-SP", value: "sr-SP",
progress: 2, progress: 4,
}, },
{ {
name: "Slovenian", name: "Slovenian",
value: "sl-SI", value: "sl-SI",
progress: 47, progress: 49,
}, },
{ {
name: "Slovak", name: "Slovak",
value: "sk-SK", value: "sk-SK",
progress: 99, progress: 97,
}, },
{ {
name: "Pусский (Russian)", name: "Pусский (Russian)",
value: "ru-RU", value: "ru-RU",
progress: 31, progress: 99,
}, },
{ {
name: "Română (Romanian)", name: "Română (Romanian)",
value: "ro-RO", value: "ro-RO",
progress: 12, progress: 32,
}, },
{ {
name: "Português (Portuguese)", name: "Português (Portuguese)",
value: "pt-PT", value: "pt-PT",
progress: 69, progress: 99,
}, },
{ {
name: "Português do Brasil (Brazilian Portuguese)", name: "Português do Brasil (Brazilian Portuguese)",
value: "pt-BR", value: "pt-BR",
progress: 97, progress: 98,
}, },
{ {
name: "Polski (Polish)", name: "Polski (Polish)",
value: "pl-PL", value: "pl-PL",
progress: 99, progress: 97,
}, },
{ {
name: "Norsk (Norwegian)", name: "Norsk (Norwegian)",
value: "no-NO", value: "no-NO",
progress: 73, progress: 85,
}, },
{ {
name: "Nederlands (Dutch)", name: "Nederlands (Dutch)",
value: "nl-NL", value: "nl-NL",
progress: 100, progress: 98,
}, },
{ {
name: "Latvian", name: "Latvian",
value: "lv-LV", value: "lv-LV",
progress: 0, progress: 1,
}, },
{ {
name: "Lithuanian", name: "Lithuanian",
value: "lt-LT", value: "lt-LT",
progress: 99, progress: 97,
}, },
{ {
name: "한국어 (Korean)", name: "한국어 (Korean)",
value: "ko-KR", value: "ko-KR",
progress: 3, progress: 5,
}, },
{ {
name: "日本語 (Japanese)", name: "日本語 (Japanese)",
value: "ja-JP", value: "ja-JP",
progress: 9, progress: 11,
}, },
{ {
name: "Italiano (Italian)", name: "Italiano (Italian)",
value: "it-IT", value: "it-IT",
progress: 98, progress: 96,
}, },
{ {
name: "Magyar (Hungarian)", name: "Magyar (Hungarian)",
value: "hu-HU", value: "hu-HU",
progress: 43, progress: 99,
}, },
{ {
name: "Croatian", name: "Croatian",
value: "hr-HR", value: "hr-HR",
progress: 100, progress: 97,
}, },
{ {
name: "עברית (Hebrew)", name: "עברית (Hebrew)",
@ -123,27 +123,27 @@ export const LOCALES = [
{ {
name: "Galician", name: "Galician",
value: "gl-ES", value: "gl-ES",
progress: 0, progress: 1,
}, },
{ {
name: "Français (French)", name: "Français (French)",
value: "fr-FR", value: "fr-FR",
progress: 100, progress: 99,
}, },
{ {
name: "French, Canada", name: "French, Canada",
value: "fr-CA", value: "fr-CA",
progress: 54, progress: 97,
}, },
{ {
name: "Suomi (Finnish)", name: "Suomi (Finnish)",
value: "fi-FI", value: "fi-FI",
progress: 31, progress: 95,
}, },
{ {
name: "Español (Spanish)", name: "Español (Spanish)",
value: "es-ES", value: "es-ES",
progress: 59, progress: 76,
}, },
{ {
name: "American English", name: "American English",
@ -153,46 +153,46 @@ export const LOCALES = [
{ {
name: "British English", name: "British English",
value: "en-GB", value: "en-GB",
progress: 2, progress: 4,
}, },
{ {
name: "Ελληνικά (Greek)", name: "Ελληνικά (Greek)",
value: "el-GR", value: "el-GR",
progress: 33, progress: 35,
}, },
{ {
name: "Deutsch (German)", name: "Deutsch (German)",
value: "de-DE", value: "de-DE",
progress: 100, progress: 99,
}, },
{ {
name: "Dansk (Danish)", name: "Dansk (Danish)",
value: "da-DK", value: "da-DK",
progress: 90, progress: 100,
}, },
{ {
name: "Čeština (Czech)", name: "Čeština (Czech)",
value: "cs-CZ", value: "cs-CZ",
progress: 60, progress: 66,
}, },
{ {
name: "Català (Catalan)", name: "Català (Catalan)",
value: "ca-ES", value: "ca-ES",
progress: 54, progress: 61,
}, },
{ {
name: "Bulgarian", name: "Bulgarian",
value: "bg-BG", value: "bg-BG",
progress: 13, progress: 99,
}, },
{ {
name: "العربية (Arabic)", name: "العربية (Arabic)",
value: "ar-SA", value: "ar-SA",
progress: 7, progress: 16,
}, },
{ {
name: "Afrikaans (Afrikaans)", name: "Afrikaans (Afrikaans)",
value: "af-ZA", value: "af-ZA",
progress: 0, progress: 96,
}, },
] ]

View file

@ -868,7 +868,9 @@
"user-can-invite-other-to-group": "User can invite other to group", "user-can-invite-other-to-group": "User can invite other to group",
"user-can-manage-group": "User can manage group", "user-can-manage-group": "User can manage group",
"user-can-organize-group-data": "User can organize group data", "user-can-organize-group-data": "User can organize group data",
"enable-advanced-features": "Enable advanced features" "enable-advanced-features": "Enable advanced features",
"it-looks-like-this-is-your-first-time-logging-in": "It looks like this is your first time logging in.",
"dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Don't want to see this anymore? Be sure to change your email in your user settings!"
}, },
"language-dialog": { "language-dialog": {
"translated": "translated", "translated": "translated",

View file

@ -34,6 +34,9 @@ export interface AppInfo {
demoStatus: boolean; demoStatus: boolean;
allowSignup: boolean; allowSignup: boolean;
} }
export interface AppStartupInfo {
isFirstLogin: boolean;
}
export interface AppStatistics { export interface AppStatistics {
totalRecipes: number; totalRecipes: number;
totalUsers: number; totalUsers: number;
@ -41,6 +44,22 @@ export interface AppStatistics {
uncategorizedRecipes: number; uncategorizedRecipes: number;
untaggedRecipes: number; untaggedRecipes: number;
} }
export interface AppTheme {
lightPrimary?: string;
lightAccent?: string;
lightSecondary?: string;
lightSuccess?: string;
lightInfo?: string;
lightWarning?: string;
lightError?: string;
darkPrimary?: string;
darkAccent?: string;
darkSecondary?: string;
darkSuccess?: string;
darkInfo?: string;
darkWarning?: string;
darkError?: string;
}
export interface BackupOptions { export interface BackupOptions {
recipes?: boolean; recipes?: boolean;
settings?: boolean; settings?: boolean;

View file

@ -6,7 +6,14 @@
*/ */
export type WebhookType = "mealplan"; export type WebhookType = "mealplan";
export type SupportedMigrations = "nextcloud" | "chowdown" | "copymethat" | "paprika" | "mealie_alpha" | "tandoor" | "plantoeat"; export type SupportedMigrations =
| "nextcloud"
| "chowdown"
| "copymethat"
| "paprika"
| "mealie_alpha"
| "tandoor"
| "plantoeat";
export interface CreateGroupPreferences { export interface CreateGroupPreferences {
privateGroup?: boolean; privateGroup?: boolean;
@ -263,39 +270,56 @@ export interface RecipeIngredient {
} }
export interface IngredientUnit { export interface IngredientUnit {
name: string; name: string;
pluralName?: string;
description?: string; description?: string;
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
}; };
fraction?: boolean; fraction?: boolean;
abbreviation?: string; abbreviation?: string;
pluralAbbreviation?: string;
useAbbreviation?: boolean; useAbbreviation?: boolean;
aliases?: IngredientUnitAlias[];
id: string; id: string;
createdAt?: string; createdAt?: string;
updateAt?: string; updateAt?: string;
} }
export interface IngredientUnitAlias {
name: string;
}
export interface CreateIngredientUnit { export interface CreateIngredientUnit {
name: string; name: string;
pluralName?: string;
description?: string; description?: string;
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
}; };
fraction?: boolean; fraction?: boolean;
abbreviation?: string; abbreviation?: string;
pluralAbbreviation?: string;
useAbbreviation?: boolean; useAbbreviation?: boolean;
aliases?: CreateIngredientUnitAlias[];
}
export interface CreateIngredientUnitAlias {
name: string;
} }
export interface IngredientFood { export interface IngredientFood {
name: string; name: string;
pluralName?: string;
description?: string; description?: string;
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
}; };
labelId?: string; labelId?: string;
aliases?: IngredientFoodAlias[];
id: string; id: string;
label?: MultiPurposeLabelSummary; label?: MultiPurposeLabelSummary;
createdAt?: string; createdAt?: string;
updateAt?: string; updateAt?: string;
} }
export interface IngredientFoodAlias {
name: string;
}
export interface MultiPurposeLabelSummary { export interface MultiPurposeLabelSummary {
name: string; name: string;
color?: string; color?: string;
@ -304,11 +328,16 @@ export interface MultiPurposeLabelSummary {
} }
export interface CreateIngredientFood { export interface CreateIngredientFood {
name: string; name: string;
pluralName?: string;
description?: string; description?: string;
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
}; };
labelId?: string; labelId?: string;
aliases?: CreateIngredientFoodAlias[];
}
export interface CreateIngredientFoodAlias {
name: string;
} }
export interface ShoppingListCreate { export interface ShoppingListCreate {
name?: string; name?: string;

View file

@ -465,7 +465,7 @@ export interface ScrapeRecipe {
export interface ScrapeRecipeTest { export interface ScrapeRecipeTest {
url: string; url: string;
} }
export interface SlugResponse { } export interface SlugResponse {}
export interface TagIn { export interface TagIn {
name: string; name: string;
} }

View file

@ -5,6 +5,7 @@
/* Do not modify it by hand - just update the pydantic models and then re-run the script /* Do not modify it by hand - just update the pydantic models and then re-run the script
*/ */
export type OrderByNullPosition = "first" | "last";
export type OrderDirection = "asc" | "desc"; export type OrderDirection = "asc" | "desc";
export interface ErrorResponse { export interface ErrorResponse {
@ -19,6 +20,7 @@ export interface PaginationQuery {
page?: number; page?: number;
perPage?: number; perPage?: number;
orderBy?: string; orderBy?: string;
orderByNullPosition?: OrderByNullPosition;
orderDirection?: OrderDirection & string; orderDirection?: OrderDirection & string;
queryFilter?: string; queryFilter?: string;
paginationSeed?: string; paginationSeed?: string;

View file

@ -7,6 +7,18 @@
'bg-off-white': !$vuetify.theme.dark && !isDark, 'bg-off-white': !$vuetify.theme.dark && !isDark,
}" }"
> >
<v-alert v-if="isFirstLogin" class="my-4" type="info" icon="mdi-information">
<div>
<p class="mb-3">
{{ $tc('user.it-looks-like-this-is-your-first-time-logging-in')}}
</p>
<p class="mb-1"><strong>{{ $tc('user.username') }}:</strong> changeme@example.com</p>
<p class="mb-3"><strong>{{ $tc('user.password') }}:</strong> MyPassword</p>
<p>
{{ $tc('user.dont-want-to-see-this-anymore-be-sure-to-change-your-email') }}
</p>
</div>
</v-alert>
<v-card tag="section" class="d-flex flex-column align-center" width="600px"> <v-card tag="section" class="d-flex flex-column align-center" width="600px">
<v-toolbar width="100%" color="primary" class="d-flex justify-center mb-4" dark> <v-toolbar width="100%" color="primary" class="d-flex justify-center mb-4" dark>
<v-toolbar-title class="headline text-h4"> Mealie </v-toolbar-title> <v-toolbar-title class="headline text-h4"> Mealie </v-toolbar-title>
@ -101,12 +113,14 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, useContext, computed, reactive, useRouter } from "@nuxtjs/composition-api"; import { defineComponent, ref, useContext, computed, reactive, useRouter, useAsync } from "@nuxtjs/composition-api";
import { useDark, whenever } from "@vueuse/core"; import { useDark, whenever } from "@vueuse/core";
import { useLoggedInState } from "~/composables/use-logged-in-state"; import { useLoggedInState } from "~/composables/use-logged-in-state";
import { useAppInfo } from "~/composables/api"; import { useAppInfo } from "~/composables/api";
import { usePasswordField } from "~/composables/use-passwords"; import { usePasswordField } from "~/composables/use-passwords";
import { alert } from "~/composables/use-toast"; import { alert } from "~/composables/use-toast";
import { useAsyncKey } from "~/composables/use-utils";
import { AppStartupInfo } from "~/lib/api/types/admin";
export default defineComponent({ export default defineComponent({
layout: "blank", layout: "blank",
@ -115,7 +129,7 @@ export default defineComponent({
const isDark = useDark(); const isDark = useDark();
const router = useRouter(); const router = useRouter();
const { $auth, i18n } = useContext(); const { $auth, i18n, $axios } = useContext();
const { loggedIn } = useLoggedInState(); const { loggedIn } = useLoggedInState();
const groupSlug = computed(() => $auth.user?.groupSlug); const groupSlug = computed(() => $auth.user?.groupSlug);
@ -133,6 +147,13 @@ export default defineComponent({
remember: false, remember: false,
}); });
const isFirstLogin = ref(false)
useAsync(async () => {
const data = await $axios.get<AppStartupInfo>("/api/app/about/startup-info");
isFirstLogin.value = data.data.isFirstLogin;
}, useAsyncKey());
const loggingIn = ref(false); const loggingIn = ref(false);
const appInfo = useAppInfo(); const appInfo = useAppInfo();
@ -182,6 +203,7 @@ export default defineComponent({
passwordIcon, passwordIcon,
inputType, inputType,
togglePasswordShow, togglePasswordShow,
isFirstLogin
}; };
}, },

View file

@ -104,7 +104,6 @@ async def system_startup():
indent=4, indent=4,
exclude={ exclude={
"SECRET", "SECRET",
"DEFAULT_PASSWORD",
"SFTP_PASSWORD", "SFTP_PASSWORD",
"SFTP_USERNAME", "SFTP_USERNAME",
"DB_URL", # replace by DB_URL_PUBLIC for logs "DB_URL", # replace by DB_URL_PUBLIC for logs

View file

@ -85,8 +85,17 @@ class AppSettings(BaseSettings):
return self.DB_PROVIDER.db_url_public if self.DB_PROVIDER else None return self.DB_PROVIDER.db_url_public if self.DB_PROVIDER else None
DEFAULT_GROUP: str = "Home" DEFAULT_GROUP: str = "Home"
DEFAULT_EMAIL: str = "changeme@example.com"
DEFAULT_PASSWORD: str = "MyPassword" _DEFAULT_EMAIL: str = "changeme@example.com"
"""
This is the default email used for the first user created in the database. This is only used if no users
exist in the database. it should no longer be set by end users.
"""
_DEFAULT_PASSWORD: str = "MyPassword"
"""
This is the default password used for the first user created in the database. This is only used if no users
exist in the database. it should no longer be set by end users.
"""
# =============================================== # ===============================================
# Email Configuration # Email Configuration

View file

@ -13,7 +13,7 @@ def dev_users() -> list[dict]:
"full_name": "Jason", "full_name": "Jason",
"username": "jason", "username": "jason",
"email": "jason@example.com", "email": "jason@example.com",
"password": hash_password(settings.DEFAULT_PASSWORD), "password": hash_password(settings._DEFAULT_PASSWORD),
"group": settings.DEFAULT_GROUP, "group": settings.DEFAULT_GROUP,
"admin": False, "admin": False,
}, },
@ -21,7 +21,7 @@ def dev_users() -> list[dict]:
"full_name": "Bob", "full_name": "Bob",
"username": "bob", "username": "bob",
"email": "bob@example.com", "email": "bob@example.com",
"password": hash_password(settings.DEFAULT_PASSWORD), "password": hash_password(settings._DEFAULT_PASSWORD),
"group": settings.DEFAULT_GROUP, "group": settings.DEFAULT_GROUP,
"admin": False, "admin": False,
}, },
@ -29,7 +29,7 @@ def dev_users() -> list[dict]:
"full_name": "Sarah", "full_name": "Sarah",
"username": "sarah", "username": "sarah",
"email": "sarah@example.com", "email": "sarah@example.com",
"password": hash_password(settings.DEFAULT_PASSWORD), "password": hash_password(settings._DEFAULT_PASSWORD),
"group": settings.DEFAULT_GROUP, "group": settings.DEFAULT_GROUP,
"admin": False, "admin": False,
}, },
@ -37,7 +37,7 @@ def dev_users() -> list[dict]:
"full_name": "Sammy", "full_name": "Sammy",
"username": "sammy", "username": "sammy",
"email": "sammy@example.com", "email": "sammy@example.com",
"password": hash_password(settings.DEFAULT_PASSWORD), "password": hash_password(settings._DEFAULT_PASSWORD),
"group": settings.DEFAULT_GROUP, "group": settings.DEFAULT_GROUP,
"admin": False, "admin": False,
}, },
@ -48,8 +48,8 @@ def default_user_init(db: AllRepositories):
default_user = { default_user = {
"full_name": "Change Me", "full_name": "Change Me",
"username": "admin", "username": "admin",
"email": settings.DEFAULT_EMAIL, "email": settings._DEFAULT_EMAIL,
"password": hash_password(settings.DEFAULT_PASSWORD), "password": hash_password(settings._DEFAULT_PASSWORD),
"group": settings.DEFAULT_GROUP, "group": settings.DEFAULT_GROUP,
"admin": True, "admin": True,
} }

View file

@ -1,8 +1,11 @@
from fastapi import APIRouter, Response from fastapi import APIRouter, Depends, Response
from sqlalchemy.orm.session import Session
from mealie.core.config import get_app_settings from mealie.core.config import get_app_settings
from mealie.core.settings.static import APP_VERSION from mealie.core.settings.static import APP_VERSION
from mealie.schema.admin.about import AppInfo, AppTheme from mealie.db.db_setup import generate_session
from mealie.db.models.users.users import User
from mealie.schema.admin.about import AppInfo, AppStartupInfo, AppTheme
router = APIRouter(prefix="/about") router = APIRouter(prefix="/about")
@ -20,6 +23,21 @@ def get_app_info():
) )
@router.get("/startup-info", response_model=AppStartupInfo)
def get_startup_info(session: Session = Depends(generate_session)):
"""returns helpful startup information"""
settings = get_app_settings()
is_first_login = False
with session as db:
if db.query(User).filter_by(email=settings._DEFAULT_EMAIL).count() > 0:
is_first_login = True
return AppStartupInfo(
is_first_login=is_first_login,
)
@router.get("/theme", response_model=AppTheme) @router.get("/theme", response_model=AppTheme)
def get_app_theme(resp: Response): def get_app_theme(resp: Response):
"""Get's the current theme settings""" """Get's the current theme settings"""

View file

@ -1,5 +1,5 @@
# This file is auto-generated by gen_schema_exports.py # This file is auto-generated by gen_schema_exports.py
from .about import AdminAboutInfo, AppInfo, AppStatistics, CheckAppConfig, DockerVolumeText from .about import AdminAboutInfo, AppInfo, AppStartupInfo, AppStatistics, AppTheme, CheckAppConfig, DockerVolumeText
from .backup import AllBackups, BackupFile, BackupOptions, CreateBackup, ImportJob from .backup import AllBackups, BackupFile, BackupOptions, CreateBackup, ImportJob
from .email import EmailReady, EmailSuccess, EmailTest from .email import EmailReady, EmailSuccess, EmailTest
from .maintenance import MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary from .maintenance import MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary
@ -27,7 +27,9 @@ __all__ = [
"MaintenanceSummary", "MaintenanceSummary",
"AdminAboutInfo", "AdminAboutInfo",
"AppInfo", "AppInfo",
"AppStartupInfo",
"AppStatistics", "AppStatistics",
"AppTheme",
"CheckAppConfig", "CheckAppConfig",
"DockerVolumeText", "DockerVolumeText",
"EmailReady", "EmailReady",

View file

@ -34,6 +34,15 @@ class AppTheme(MealieModel):
dark_error: str = "#EF5350" dark_error: str = "#EF5350"
class AppStartupInfo(MealieModel):
is_first_login: bool
"""
The applications best guess that a user hasn't logged in. Currently, it really
on indicates that the 'changeme@example.com' user is still in the database. Once
it is removed, this will always return False.
"""
class AdminAboutInfo(AppInfo): class AdminAboutInfo(AppInfo):
versionLatest: str versionLatest: str
api_port: int api_port: int

View file

@ -92,6 +92,25 @@ __all__ = [
"RecipeToolOut", "RecipeToolOut",
"RecipeToolResponse", "RecipeToolResponse",
"RecipeToolSave", "RecipeToolSave",
"RecipeTimelineEventCreate",
"RecipeTimelineEventIn",
"RecipeTimelineEventOut",
"RecipeTimelineEventPagination",
"RecipeTimelineEventUpdate",
"TimelineEventImage",
"TimelineEventType",
"RecipeAsset",
"RecipeSettings",
"RecipeShareToken",
"RecipeShareTokenCreate",
"RecipeShareTokenSave",
"RecipeShareTokenSummary",
"RecipeDuplicate",
"RecipeSlug",
"RecipeZipTokenResponse",
"SlugResponse",
"UpdateImageResponse",
"RecipeNote",
"CategoryBase", "CategoryBase",
"CategoryIn", "CategoryIn",
"CategoryOut", "CategoryOut",
@ -102,6 +121,12 @@ __all__ = [
"TagIn", "TagIn",
"TagOut", "TagOut",
"TagSave", "TagSave",
"RecipeCommentCreate",
"RecipeCommentOut",
"RecipeCommentPagination",
"RecipeCommentSave",
"RecipeCommentUpdate",
"UserBase",
"AssignCategories", "AssignCategories",
"AssignSettings", "AssignSettings",
"AssignTags", "AssignTags",
@ -109,34 +134,10 @@ __all__ = [
"ExportBase", "ExportBase",
"ExportRecipes", "ExportRecipes",
"ExportTypes", "ExportTypes",
"RecipeShareToken",
"RecipeShareTokenCreate",
"RecipeShareTokenSave",
"RecipeShareTokenSummary",
"ScrapeRecipe",
"ScrapeRecipeTest",
"RecipeCommentCreate",
"RecipeCommentOut",
"RecipeCommentPagination",
"RecipeCommentSave",
"RecipeCommentUpdate",
"UserBase",
"RecipeImageTypes",
"CreateRecipe",
"CreateRecipeBulk",
"CreateRecipeByUrlBulk",
"Recipe",
"RecipeCategory",
"RecipeCategoryPagination",
"RecipeLastMade",
"RecipePagination",
"RecipeSummary",
"RecipeTag",
"RecipeTagPagination",
"RecipeTool",
"RecipeToolPagination",
"IngredientReferences", "IngredientReferences",
"RecipeStep", "RecipeStep",
"RecipeImageTypes",
"Nutrition",
"CreateIngredientFood", "CreateIngredientFood",
"CreateIngredientFoodAlias", "CreateIngredientFoodAlias",
"CreateIngredientUnit", "CreateIngredientUnit",
@ -159,20 +160,19 @@ __all__ = [
"SaveIngredientFood", "SaveIngredientFood",
"SaveIngredientUnit", "SaveIngredientUnit",
"UnitFoodBase", "UnitFoodBase",
"RecipeAsset", "CreateRecipe",
"RecipeTimelineEventCreate", "CreateRecipeBulk",
"RecipeTimelineEventIn", "CreateRecipeByUrlBulk",
"RecipeTimelineEventOut", "Recipe",
"RecipeTimelineEventPagination", "RecipeCategory",
"RecipeTimelineEventUpdate", "RecipeCategoryPagination",
"TimelineEventImage", "RecipeLastMade",
"TimelineEventType", "RecipePagination",
"RecipeDuplicate", "RecipeSummary",
"RecipeSlug", "RecipeTag",
"RecipeZipTokenResponse", "RecipeTagPagination",
"SlugResponse", "RecipeTool",
"UpdateImageResponse", "RecipeToolPagination",
"Nutrition", "ScrapeRecipe",
"RecipeSettings", "ScrapeRecipeTest",
"RecipeNote",
] ]

View file

@ -1,5 +1,5 @@
# This file is auto-generated by gen_schema_exports.py # This file is auto-generated by gen_schema_exports.py
from .pagination import OrderDirection, PaginationBase, PaginationQuery, RecipeSearchQuery from .pagination import OrderByNullPosition, OrderDirection, PaginationBase, PaginationQuery, RecipeSearchQuery
from .query_filter import LogicalOperator, QueryFilter, QueryFilterComponent, RelationalKeyword, RelationalOperator from .query_filter import LogicalOperator, QueryFilter, QueryFilterComponent, RelationalKeyword, RelationalOperator
from .query_search import SearchFilter from .query_search import SearchFilter
from .responses import ErrorResponse, FileTokenResponse, SuccessResponse from .responses import ErrorResponse, FileTokenResponse, SuccessResponse
@ -15,6 +15,7 @@ __all__ = [
"QueryFilterComponent", "QueryFilterComponent",
"RelationalKeyword", "RelationalKeyword",
"RelationalOperator", "RelationalOperator",
"OrderByNullPosition",
"OrderDirection", "OrderDirection",
"PaginationBase", "PaginationBase",
"PaginationQuery", "PaginationQuery",

View file

@ -116,7 +116,7 @@ class UserOut(UserBase):
@property @property
def is_default_user(self) -> bool: def is_default_user(self) -> bool:
return self.email == settings.DEFAULT_EMAIL.strip().lower() return self.email == settings._DEFAULT_EMAIL.strip().lower()
@classmethod @classmethod
def loader_options(cls) -> list[LoaderOption]: def loader_options(cls) -> list[LoaderOption]:

View file

@ -10,7 +10,7 @@ from tests.utils import api_routes
def admin_token(api_client: TestClient): def admin_token(api_client: TestClient):
settings = get_app_settings() settings = get_app_settings()
form_data = {"username": settings.DEFAULT_EMAIL, "password": settings.DEFAULT_PASSWORD} form_data = {"username": settings._DEFAULT_EMAIL, "password": settings._DEFAULT_PASSWORD}
return utils.login(form_data, api_client) return utils.login(form_data, api_client)
@ -18,7 +18,7 @@ def admin_token(api_client: TestClient):
def admin_user(api_client: TestClient): def admin_user(api_client: TestClient):
settings = get_app_settings() settings = get_app_settings()
form_data = {"username": settings.DEFAULT_EMAIL, "password": settings.DEFAULT_PASSWORD} form_data = {"username": settings._DEFAULT_EMAIL, "password": settings._DEFAULT_PASSWORD}
token = utils.login(form_data, api_client) token = utils.login(form_data, api_client)
@ -33,7 +33,7 @@ def admin_user(api_client: TestClient):
yield utils.TestUser( yield utils.TestUser(
_group_id=user_data.get("groupId"), _group_id=user_data.get("groupId"),
user_id=user_data.get("id"), user_id=user_data.get("id"),
password=settings.DEFAULT_PASSWORD, password=settings._DEFAULT_PASSWORD,
username=user_data.get("username"), username=user_data.get("username"),
email=user_data.get("email"), email=user_data.get("email"),
token=token, token=token,

View file

@ -36,7 +36,7 @@ def test_init_superuser(api_client: TestClient, admin_user: TestUser):
assert admin_data["groupId"] == admin_user.group_id assert admin_data["groupId"] == admin_user.group_id
assert admin_data["fullName"] == "Change Me" assert admin_data["fullName"] == "Change Me"
assert admin_data["email"] == settings.DEFAULT_EMAIL assert admin_data["email"] == settings._DEFAULT_EMAIL
def test_create_user(api_client: TestClient, admin_token): def test_create_user(api_client: TestClient, admin_token):
@ -95,7 +95,7 @@ def test_update_other_user_as_not_admin(api_client: TestClient, unique_user: Tes
update_data = { update_data = {
"id": unique_user.user_id, "id": unique_user.user_id,
"fullName": "Updated Name", "fullName": "Updated Name",
"email": settings.DEFAULT_EMAIL, "email": settings._DEFAULT_EMAIL,
"group": "Home", "group": "Home",
"admin": True, "admin": True,
} }

View file

@ -14,7 +14,7 @@ from tests.utils.fixture_schemas import TestUser
def test_failed_login(api_client: TestClient): def test_failed_login(api_client: TestClient):
settings = get_app_settings() settings = get_app_settings()
form_data = {"username": settings.DEFAULT_EMAIL, "password": "WRONG_PASSWORD"} form_data = {"username": settings._DEFAULT_EMAIL, "password": "WRONG_PASSWORD"}
response = api_client.post(api_routes.auth_token, data=form_data) response = api_client.post(api_routes.auth_token, data=form_data)
assert response.status_code == 401 assert response.status_code == 401
@ -23,7 +23,7 @@ def test_failed_login(api_client: TestClient):
def test_superuser_login(api_client: TestClient, admin_token): def test_superuser_login(api_client: TestClient, admin_token):
settings = get_app_settings() settings = get_app_settings()
form_data = {"username": settings.DEFAULT_EMAIL, "password": settings.DEFAULT_PASSWORD} form_data = {"username": settings._DEFAULT_EMAIL, "password": settings._DEFAULT_PASSWORD}
response = api_client.post(api_routes.auth_token, data=form_data) response = api_client.post(api_routes.auth_token, data=form_data)
assert response.status_code == 200 assert response.status_code == 200

View file

@ -9,7 +9,6 @@ from mealie.core.settings.settings import AppSettings
def test_non_default_settings(monkeypatch): def test_non_default_settings(monkeypatch):
monkeypatch.setenv("DEFAULT_GROUP", "Test Group") monkeypatch.setenv("DEFAULT_GROUP", "Test Group")
monkeypatch.setenv("DEFAULT_PASSWORD", "Test Password")
monkeypatch.setenv("API_PORT", "8000") monkeypatch.setenv("API_PORT", "8000")
monkeypatch.setenv("API_DOCS", "False") monkeypatch.setenv("API_DOCS", "False")
@ -17,7 +16,6 @@ def test_non_default_settings(monkeypatch):
app_settings = get_app_settings() app_settings = get_app_settings()
assert app_settings.DEFAULT_GROUP == "Test Group" assert app_settings.DEFAULT_GROUP == "Test Group"
assert app_settings.DEFAULT_PASSWORD == "Test Password"
assert app_settings.API_PORT == 8000 assert app_settings.API_PORT == 8000
assert app_settings.API_DOCS is False assert app_settings.API_DOCS is False

View file

@ -45,6 +45,10 @@ admin_users_unlock = "/api/admin/users/unlock"
"""`/api/admin/users/unlock`""" """`/api/admin/users/unlock`"""
app_about = "/api/app/about" app_about = "/api/app/about"
"""`/api/app/about`""" """`/api/app/about`"""
app_about_startup_info = "/api/app/about/startup-info"
"""`/api/app/about/startup-info`"""
app_about_theme = "/api/app/about/theme"
"""`/api/app/about/theme`"""
auth_refresh = "/api/auth/refresh" auth_refresh = "/api/auth/refresh"
"""`/api/auth/refresh`""" """`/api/auth/refresh`"""
auth_token = "/api/auth/token" auth_token = "/api/auth/token"
@ -183,6 +187,8 @@ users_reset_password = "/api/users/reset-password"
"""`/api/users/reset-password`""" """`/api/users/reset-password`"""
users_self = "/api/users/self" users_self = "/api/users/self"
"""`/api/users/self`""" """`/api/users/self`"""
users_self_group = "/api/users/self/group"
"""`/api/users/self/group`"""
utils_download = "/api/utils/download" utils_download = "/api/utils/download"
"""`/api/utils/download`""" """`/api/utils/download`"""
validators_group = "/api/validators/group" validators_group = "/api/validators/group"