diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index cbac5071b..07fd5786e 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,8 +1,8 @@ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.224.2/containers/python-3/.devcontainer/base.Dockerfile # [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster -ARG VARIANT="3.10-bullseye" -FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} +ARG VARIANT="3.12-bullseye" +FROM mcr.microsoft.com/devcontainers/python:${VARIANT} # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 ARG NODE_VERSION="none" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fed52b188..31d8f3427 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -9,7 +9,7 @@ // Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6 // Append -bullseye or -buster to pin to an OS version. // Use -bullseye variants on local on arm64/Apple Silicon. - "VARIANT": "3.10-bullseye", + "VARIANT": "3.12-bullseye", // Options "NODE_VERSION": "16" } diff --git a/.github/workflows/partial-backend.yml b/.github/workflows/partial-backend.yml index fae5dfd8b..b0772d181 100644 --- a/.github/workflows/partial-backend.yml +++ b/.github/workflows/partial-backend.yml @@ -47,7 +47,7 @@ jobs: - name: Set up python uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - name: Install Poetry uses: snok/install-poetry@v1 diff --git a/.github/workflows/scheduled-checks.yml b/.github/workflows/scheduled-checks.yml index 0134d60cd..337907418 100644 --- a/.github/workflows/scheduled-checks.yml +++ b/.github/workflows/scheduled-checks.yml @@ -18,7 +18,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - name: Set PY shell: bash diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e972d9ce4..2e8de8afb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: exclude: ^tests/data/ - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.7.3 + rev: v0.8.2 hooks: - id: ruff - id: ruff-format diff --git a/Taskfile.yml b/Taskfile.yml index a9ca44ac0..70f6f23dd 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -151,7 +151,7 @@ tasks: py:migrate: desc: generates a new database migration file e.g. task py:migrate -- "add new column" cmds: - - poetry run alembic revision --autogenerate -m "{{ .CLI_ARGS }}" + - poetry run alembic --config mealie/alembic/alembic.ini revision --autogenerate -m "{{ .CLI_ARGS }}" - task: py:format ui:build: diff --git a/dev/code-generation/gen_ts_types.py b/dev/code-generation/gen_ts_types.py index b15f7ee9d..408262f25 100644 --- a/dev/code-generation/gen_ts_types.py +++ b/dev/code-generation/gen_ts_types.py @@ -1,3 +1,4 @@ +import re from pathlib import Path from jinja2 import Template @@ -64,7 +65,112 @@ def generate_global_components_types() -> None: # Pydantic To Typescript Generator -def generate_typescript_types() -> None: +def generate_typescript_types() -> None: # noqa: C901 + def contains_number(s: str) -> bool: + return bool(re.search(r"\d", s)) + + def remove_numbers(s: str) -> str: + return re.sub(r"\d", "", s) + + def extract_type_name(line: str) -> str: + # Looking for "export type EnumName = enumVal1 | enumVal2 | ..." + if not (line.startswith("export type") and "=" in line): + return "" + + return line.split(" ")[2] + + def extract_property_type_name(line: str) -> str: + # Looking for " fieldName: FieldType;" or " fieldName: FieldType & string;" + if not (line.startswith(" ") and ":" in line): + return "" + + return line.split(":")[1].strip().split(";")[0] + + def extract_interface_name(line: str) -> str: + # Looking for "export interface InterfaceName {" + if not (line.startswith("export interface") and "{" in line): + return "" + + return line.split(" ")[2] + + def is_comment_line(line: str) -> bool: + s = line.strip() + return s.startswith("/*") or s.startswith("*") + + def clean_output_file(file: Path) -> None: + """ + json2ts generates duplicate types off of our enums and appends a number to the end of the type name. + Our Python code (hopefully) doesn't have any duplicate enum names, or types with numbers in them, + so we can safely remove the numbers. + + To do this, we read the output line-by-line and replace any type names that contain numbers with + the same type name, but without the numbers. + + Note: the issue arrises from the JSON package json2ts, not the Python package pydantic2ts, + otherwise we could just fix pydantic2ts. + """ + + # First pass: build a map of type names to their numberless counterparts and lines to skip + replacement_map = {} + lines_to_skip = set() + wait_for_semicolon = False + wait_for_close_bracket = False + skip_comments = False + with open(file) as f: + for i, line in enumerate(f.readlines()): + if wait_for_semicolon: + if ";" in line: + wait_for_semicolon = False + lines_to_skip.add(i) + continue + if wait_for_close_bracket: + if "}" in line: + wait_for_close_bracket = False + lines_to_skip.add(i) + continue + + if type_name := extract_type_name(line): + if not contains_number(type_name): + continue + + replacement_map[type_name] = remove_numbers(type_name) + if ";" not in line: + wait_for_semicolon = True + lines_to_skip.add(i) + + elif type_name := extract_interface_name(line): + if not contains_number(type_name): + continue + + replacement_map[type_name] = remove_numbers(type_name) + if "}" not in line: + wait_for_close_bracket = True + lines_to_skip.add(i) + + elif skip_comments and is_comment_line(line): + lines_to_skip.add(i) + + # we've passed the opening comments and empty line at the header + elif not skip_comments and not line.strip(): + skip_comments = True + + # Second pass: rewrite or remove lines as needed. + # We have to do two passes here because definitions don't always appear in the same order as their usage. + lines = [] + with open(file) as f: + for i, line in enumerate(f.readlines()): + if i in lines_to_skip: + continue + + if type_name := extract_property_type_name(line): + if type_name in replacement_map: + line = line.replace(type_name, replacement_map[type_name]) + + lines.append(line) + + with open(file, "w") as f: + f.writelines(lines) + def path_to_module(path: Path): str_path: str = str(path) @@ -98,9 +204,10 @@ def generate_typescript_types() -> None: try: path_as_module = path_to_module(module) generate_typescript_defs(path_as_module, str(out_path), exclude=("MealieModel")) # type: ignore - except Exception as e: + clean_output_file(out_path) + except Exception: failed_modules.append(module) - log.error(f"Module Error: {e}") + log.exception(f"Module Error: {module}") log.debug("\n📁 Skipped Directories:") for skipped_dir in skipped_dirs: diff --git a/docker/Dockerfile b/docker/Dockerfile index 6c9c09066..bdee7416e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -17,7 +17,7 @@ RUN yarn generate ############################################### # Base Image - Python ############################################### -FROM python:3.10-slim as python-base +FROM python:3.12-slim as python-base ENV MEALIE_HOME="/app" @@ -109,10 +109,6 @@ COPY --from=crfpp /usr/local/bin/crf_test /usr/local/bin/crf_test COPY ./mealie $MEALIE_HOME/mealie COPY ./poetry.lock ./pyproject.toml $MEALIE_HOME/ -# Alembic -COPY ./alembic $MEALIE_HOME/alembic -COPY ./alembic.ini $MEALIE_HOME/ - # venv already has runtime deps installed we get a quicker install WORKDIR $MEALIE_HOME RUN . $VENV_PATH/bin/activate && poetry install -E pgsql --only main diff --git a/docs/docs/contributors/developers-guide/starting-dev-server.md b/docs/docs/contributors/developers-guide/starting-dev-server.md index d17772473..4edac0b18 100644 --- a/docs/docs/contributors/developers-guide/starting-dev-server.md +++ b/docs/docs/contributors/developers-guide/starting-dev-server.md @@ -32,7 +32,7 @@ Make sure the VSCode Dev Containers extension is installed, then select "Dev Con ### Prerequisites -- [Python 3.10](https://www.python.org/downloads/) +- [Python 3.12](https://www.python.org/downloads/) - [Poetry](https://python-poetry.org/docs/#installation) - [Node v16.x](https://nodejs.org/en/) - [yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable) diff --git a/docs/docs/documentation/community-guide/home-assistant.md b/docs/docs/documentation/community-guide/home-assistant.md index b88b30dbd..d11db6a8d 100644 --- a/docs/docs/documentation/community-guide/home-assistant.md +++ b/docs/docs/documentation/community-guide/home-assistant.md @@ -24,7 +24,7 @@ Make sure the url and port (`http://mealie:9000` ) matches your installation's a ```yaml rest: - - resource: "http://mealie:9000/api/groups/mealplans/today" + - resource: "http://mealie:9000/api/households/mealplans/today" method: GET headers: Authorization: Bearer <> diff --git a/docs/docs/documentation/getting-started/authentication/oidc.md b/docs/docs/documentation/getting-started/authentication/oidc.md index d385a1cb6..9eb889f02 100644 --- a/docs/docs/documentation/getting-started/authentication/oidc.md +++ b/docs/docs/documentation/getting-started/authentication/oidc.md @@ -5,7 +5,7 @@ Mealie supports 3rd party authentication via [OpenID Connect (OIDC)](https://openid.net/connect/), an identity layer built on top of OAuth2. OIDC is supported by many Identity Providers (IdP), including: - [Authentik](https://goauthentik.io/integrations/sources/oauth/#openid-connect) -- [Authelia](https://www.authelia.com/configuration/identity-providers/open-id-connect/) +- [Authelia](https://www.authelia.com/integration/openid-connect/mealie/) - [Keycloak](https://www.keycloak.org/docs/latest/securing_apps/#_oidc) - [Okta](https://www.okta.com/openid-connect/) diff --git a/docs/docs/documentation/getting-started/faq.md b/docs/docs/documentation/getting-started/faq.md index b6692f9c9..5484c718a 100644 --- a/docs/docs/documentation/getting-started/faq.md +++ b/docs/docs/documentation/getting-started/faq.md @@ -79,7 +79,7 @@ Mealie's Recipe Steps and other fields support markdown syntax and therefore sup If your account has been locked by bad password attempts, you can use an administrator account to unlock another account. Alternatively, you can unlock all accounts via a script within the container. ```shell -docker exec -it mealie-next bash +docker exec -it mealie bash python /app/mealie/scripts/reset_locked_users.py ``` @@ -89,7 +89,7 @@ python /app/mealie/scripts/reset_locked_users.py You can change your password by going to the user profile page and clicking the "Change Password" button. Alternatively you can use the following script to change your password via the CLI if you are locked out of your account. ```shell -docker exec -it mealie-next bash +docker exec -it mealie bash python /app/mealie/scripts/change_password.py ``` diff --git a/docs/docs/documentation/getting-started/installation/backend-config.md b/docs/docs/documentation/getting-started/installation/backend-config.md index e2f2156f0..6884bd6f7 100644 --- a/docs/docs/documentation/getting-started/installation/backend-config.md +++ b/docs/docs/documentation/getting-started/installation/backend-config.md @@ -95,7 +95,7 @@ Use this only when mealie is run without a webserver or reverse proxy. For usage, see [Usage - OpenID Connect](../authentication/oidc-v2.md) | Variables | Default | Description | -| ------------------------------------------------- | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +|---------------------------------------------------|:-------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | OIDC_AUTH_ENABLED | False | Enables authentication via OpenID Connect | | OIDC_SIGNUP_ENABLED | True | Enables new users to be created when signing in for the first time with OIDC | | OIDC_CONFIGURATION_URL | None | The URL to the OIDC configuration of your provider. This is usually something like https://auth.example.com/.well-known/openid-configuration | @@ -107,6 +107,7 @@ For usage, see [Usage - OpenID Connect](../authentication/oidc-v2.md) | OIDC_PROVIDER_NAME | OAuth | The provider name is shown in SSO login button. "Login with " | | OIDC_REMEMBER_ME | False | Because redirects bypass the login screen, you cant extend your session by clicking the "Remember Me" checkbox. By setting this value to true, a session will be extended as if "Remember Me" was checked | | OIDC_USER_CLAIM | email | This is the claim which Mealie will use to look up an existing user by (e.g. "email", "preferred_username") | +| OIDC_NAME_CLAIM | name | This is the claim which Mealie will use for the users Full Name | | OIDC_GROUPS_CLAIM | groups | Optional if not using `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP`. This is the claim Mealie will request from your IdP and will use to compare to `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP` to allow the user to log in to Mealie or is set as an admin. **Your IdP must be configured to grant this claim** | | OIDC_SCOPES_OVERRIDE | None | Advanced configuration used to override the scopes requested from the IdP. **Most users won't need to change this**. At a minimum, 'openid profile email' are required. | | OIDC_TLS_CACERTFILE | None | File path to Certificate Authority used to verify server certificate (e.g. `/path/to/ca.crt`) | diff --git a/docs/docs/documentation/getting-started/installation/installation-checklist.md b/docs/docs/documentation/getting-started/installation/installation-checklist.md index 417105b0e..f941a7e0d 100644 --- a/docs/docs/documentation/getting-started/installation/installation-checklist.md +++ b/docs/docs/documentation/getting-started/installation/installation-checklist.md @@ -31,7 +31,7 @@ To deploy mealie on your local network, it is highly recommended to use Docker t We've gone through a few versions of Mealie v1 deployment targets. We have settled on a single container deployment, and we've begun publishing the nightly container on github containers. If you're looking to move from the old nightly (split containers _or_ the omni image) to the new nightly, there are a few things you need to do: 1. Take a backup just in case! -2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v2.2.0` +2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v2.3.0` 3. Take the external port from the frontend container and set that as the port mapped to port `9000` on the new container. The frontend is now served on port 9000 from the new container, so it will need to be mapped for you to have access. 4. Restart the container diff --git a/docs/docs/documentation/getting-started/installation/postgres.md b/docs/docs/documentation/getting-started/installation/postgres.md index f25fc851d..b4a9d35d5 100644 --- a/docs/docs/documentation/getting-started/installation/postgres.md +++ b/docs/docs/documentation/getting-started/installation/postgres.md @@ -7,7 +7,7 @@ PostgreSQL might be considered if you need to support many concurrent users. In ```yaml services: mealie: - image: ghcr.io/mealie-recipes/mealie:v2.2.0 # (3) + image: ghcr.io/mealie-recipes/mealie:v2.3.0 # (3) container_name: mealie restart: always ports: @@ -24,8 +24,6 @@ services: PUID: 1000 PGID: 1000 TZ: America/Anchorage - MAX_WORKERS: 1 - WEB_CONCURRENCY: 1 BASE_URL: https://mealie.yourdomain.com # Database Settings DB_ENGINE: postgres diff --git a/docs/docs/documentation/getting-started/installation/sqlite.md b/docs/docs/documentation/getting-started/installation/sqlite.md index 8a5457fe7..f1b22bcb9 100644 --- a/docs/docs/documentation/getting-started/installation/sqlite.md +++ b/docs/docs/documentation/getting-started/installation/sqlite.md @@ -11,7 +11,7 @@ SQLite is a popular, open source, self-contained, zero-configuration database th ```yaml services: mealie: - image: ghcr.io/mealie-recipes/mealie:v2.2.0 # (3) + image: ghcr.io/mealie-recipes/mealie:v2.3.0 # (3) container_name: mealie restart: always ports: @@ -28,8 +28,6 @@ services: PUID: 1000 PGID: 1000 TZ: America/Anchorage - MAX_WORKERS: 1 - WEB_CONCURRENCY: 1 BASE_URL: https://mealie.yourdomain.com volumes: diff --git a/docs/docs/overrides/api.html b/docs/docs/overrides/api.html index 9cef9b578..8cc33dbaf 100644 --- a/docs/docs/overrides/api.html +++ b/docs/docs/overrides/api.html @@ -14,7 +14,7 @@
diff --git a/frontend/assets/css/main.css b/frontend/assets/css/main.css index c89c6620d..74257bd4a 100644 --- a/frontend/assets/css/main.css +++ b/frontend/assets/css/main.css @@ -48,3 +48,11 @@ .v-card__title { word-break: normal !important; } + +.text-hide-overflow { + display: inline-block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; +} diff --git a/frontend/components/Domain/QueryFilterBuilder.vue b/frontend/components/Domain/QueryFilterBuilder.vue index a0edf50fa..b87fb2461 100644 --- a/frontend/components/Domain/QueryFilterBuilder.vue +++ b/frontend/components/Domain/QueryFilterBuilder.vue @@ -253,7 +253,7 @@ - + { + const part: QueryFilterJSONPart = { + attributeName: field.name, + leftParenthesis: field.leftParenthesis, + rightParenthesis: field.rightParenthesis, + logicalOperator: field.logicalOperator?.value, + relationalOperator: field.relationalOperatorValue?.value, + }; + + if (field.fieldOptions?.length || isOrganizerType(field.type)) { + part.value = field.values.map((value) => value.toString()); + } else if (field.type === "boolean") { + part.value = field.value ? "true" : "false"; + } else { + part.value = (field.value || "").toString(); + } + + return part; + }); + + const qfJSON = { parts } as QueryFilterJSON; + console.debug(`Built query filter JSON: ${JSON.stringify(qfJSON)}`); + return qfJSON; + } + const attrs = computed(() => { const baseColMaxWidth = 55; diff --git a/frontend/components/Domain/Recipe/RecipeContextMenu.vue b/frontend/components/Domain/Recipe/RecipeContextMenu.vue index 0f847d60e..b54189251 100644 --- a/frontend/components/Domain/Recipe/RecipeContextMenu.vue +++ b/frontend/components/Domain/Recipe/RecipeContextMenu.vue @@ -51,8 +51,6 @@
+ + + + +
-
- - - {{ $t('recipe.made-this') }} - -
+
+ + + {{ $t('recipe.made-this') }} + +
@@ -125,7 +125,7 @@ export default defineComponent({ }, recipe: { type: Object as () => Recipe, - default: null, + required: true, }, }, setup(props, context) { diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePage.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePage.vue index 2489e4af1..fc4d41c9a 100644 --- a/frontend/components/Domain/Recipe/RecipePage/RecipePage.vue +++ b/frontend/components/Domain/Recipe/RecipePage/RecipePage.vue @@ -1,7 +1,7 @@